El pool de constantes Java
En un fichero de clases java el pool de constantes tiene un función similar a la tabla de símbolos en otros lenguajes. Esta constituido por una serie de referencias simbólicas a los tipos y constantes utilizados por una clase. Una referencia simbólica hacia un miembro de otra clase esta formada por el nombre completo (con paquetes) de la clase y el nombre del método o campo en dicha clase. Los miembros de la clase cuyo pool se examina también aparecen como referencias simbólicas.
En pool de constantes también existen entradas describiendo las constantes de cadena y de datos primitivos, y literales numéricos utilizados en expresiones. El contenido de dichas entradas es el valor constante.
Las referencias simbólicas son sustituidas por referencias directas (punteros o desplazamientos) hacia los miembros que apuntan durante el último paso del enlazamiento de una clase, denominado resolución. La estrategia mas común es no resolver el pool de una clase inmediatamente tras su carga, sino posponer la resolución de las entradas en el pool hasta su primer uso. Durante la resolución de una entrada que apunta hacia un miembro de otra clase se comprueba la existencia y accesibilidad de la otra clase y del campo o método declarado en ella. La accesibilidad viene dada por los modificadores private, public y protected.
El compilador genera un pool de constantes en cada fichero de clases. Cuando la maquina virtual carga una clase construye el pool de constantes runtime en la zona de datos del tipo asociada a dicha clase.
En la siguiente dirección podemos encontrar la especificación de la maquina virtual y en ella detalles sobre el pool de constantes: http://java.sun.com/docs/books/vmspec/2nd-edition/html/VMSpecTOC.doc.html
El pool de constantes en el fichero de clase es precedido de un número que proporciona la cuenta de entradas en el pool. Este número siempre excede en uno a la cantidad de entradas existentes en el pool.
Entradas en el pool
El formato de las tablas siguientes es:
NOMBRE_De_La_Entrada
nº de bytes
nombre del subcampo en la entrada
descripción
Entradas que describen valores literales
CONSTANT_Integer_Info
1 byte
tag
que identifica el tipo, en este caso tiene un
valor de 3
4 bytes
bytes
cuatro bytes sin signo con el valor de una
constante entera (int)
CONSTANT_Float_Info
1 byte
tag
que identifica el tipo, en este caso tiene
un valor de 4
4 bytes
bytes
cuatro bytes sin signo con el valor de una
constante float en orden big-endian
CONSTANT_Long_Info
1 byte
tag
5
8 bytes
bytes
ocho bytes sin signo con el valor de una
constante del tipo long en orden big-endian
CONSTANT_Double_Info
1 byte
tag
6
8 bytes
bytes
ocho bytes sin signo con el valor de una
constante del tipo double en orden big-endian
Las dos últimas entradas ocupan dos posiciones en pool. Por ejemplo si una entrada del tipo CONSTANT_Long_Info esta situada en la entrada numero 5 del pool, la siguiente entrada estaría en el numero 7. Y no existiría entrada con el número 6.
CONSTANT_String_Info
1 byte
tag
8
2 bytes
string_index
indice en el pool de una entrada del tipo
CONSTANT_Utf8_Info conteniendo el literal
de cadena correspondiente a esta entrada.
Mas adelante nos referiremos a estas cinco entradas como entradas de literales.
Entrada describiendo literales de cadena o nombres especiales
CONSTANT_Utf8_Info
1 byte
tag
1
2 bytes
length
longitud en bytes que a continuación describen
una cadena o un nombre de tipo o atributo
length bytes
bytes
caracteres en un formato Utf8 modificado
Esta entrada puede contener el valor de los literales de cadena declarados en el código fuente. Dichas cadenas se convierten a un objeto del tipo String como resultado de la resolución de la entrada. También se almacenan en este tipo de entrada los nombres completos de clases o interfaces, los nombres y descriptores de métodos y campos, y los nombres de los atributos del fichero de clase.
El formato Utf8 modificado es el siguiente:
Rango de caracteres
Representado por
'\u0001' a '\u007F'
0XXX XXXX (1 byte)
'\u0080' a '\u07FF' y '\u0000'
110X XXXX 10XX XXXX (2 bytes)
'\u0800' a '\uFFFF'
1110 XXXX 10XX XXXX 10XX XXXX (3 bytes)
Las diferencias con el formato estándar Utf8 son que no se reconocen los formatos de mas de 3 bytes y que el carácter null se codifica con dos bytes en vez de uno. Esta última diferencia evita que aparezca un byte a cero en una cadena en Java.
Entradas apuntando a tipos o miembros de tipos
CONSTANT_Class_Info
1 byte
tag
7
2 bytes
name_index
indice en el pool de una entrada del tipo
CONSTANT_Utf8_Info conteniendo el nombre
completo de una clase o interfaz, o bien
el descriptor de una matriz. Por ejemplo
[[D para double[][].
CONSTANT_NameAndType_Info
1 byte
tag
12
2 bytes
name_index
indice en el pool de una entrada del tipo
CONSTANT_Utf8_Info conteniendo el nombre
simple de un campo o método. El nombre ha
de ser un identificador java valido o
para un constructor.
2 bytes
descriptor_index
Indice en el pool de una entrada del tipo
CONSTANT_Utf8_Info conteniendo el descriptor
de un campo o método.
CONSTANT_Fieldref_Info
1 byte
tag
9
2 bytes
class_index
indice en el pool de una entrada del tipo
CONSTANT_Class_Info conteniendo el nombre
completo de la clase o interfaz donde fue
declarado el campo apuntado por esta entrada.
2 bytes
name_and_
type_index
Indice en el pool de una entrada del tipo
CONSTANT_NameAndType conteniendo indices
en pool para dos entradas del tipo
CONSTANT_Utf8_info indicando el nombre
y descriptor del campo.
CONSTANT_Methodref_Info
1 byte
tag
10
2 bytes
class_index
indice en el pool de una entrada del tipo
CONSTANT_Class_Info conteniendo el nombre
completo de la clase (no interfaz) donde
fue declarado el método apuntado por esta
entrada.
2 bytes
name_and_
type_index
Indice en el pool de una entrada del tipo
CONSTANT_NameAndType conteniendo indices
en pool para dos entradas del tipo CONSTANT_Utf8_info
indicando el nombre y descriptor del método.
CONSTANT_InterfaceMethodref_Info
1 byte
tag
11
2 bytes
class_index
indice en el pool de una entrada del tipo
CONSTANT_Class_Info conteniendo el nombre
completo de el interfaz (no clase) donde fue declarado
el campo apuntado por esta entrada.
2 bytes
name_and_
type_index
Indice en el pool de una entrada del tipo
CONSTANT_NameAndType conteniendo indices en
pool para dos entradas del tipo CONSTANT_Utf8_info
indicando el nombre y descriptor del campo.
Ejemplo 1
class Strings {
//Los Literales de cadena sean constantes,estáticos, o instancias, generan
//una entrada CONSTANT_String_Info en el pool de la clase que los declara
String string1 = "Esto es un literal de cadena" ;
final String string2 = "Esto es un literal de cadena"
static final String string3 = "Esto es un literal de cadena"
}
Cada una de las tres lineas generaría una entrada CONSTANT_String_Info en el pool de de la clase Strings. Pero, al
ser el contenido de todas las literales de cadena idéntico únicamente se genera una:
--------------------------------------------------------
8 8 tag (2) CONSTANT_String
0 0 string_index Esto es un literal de cadena
19 11
--------------------------------------------------------
La cual apunta a una entrada CONSTANT_Utf8_Info conteniendo la cadena:
--------------------------------------------------------
1 1 tag (19) CONSTANT_Utf8
0 0 length 28
28 1c
69 45 E bytes Esto es un literal de cadena
115 73 s
116 74 t
111 6f o
32 20
101 65 e
115 73 s
32 20
117 75 u
110 6e n
32 20
108 6c l
105 69 i
116 74 t
101 65 e
114 72 r
97 61 a
108 6c l
32 20
100 64 d
101 65 e
32 20
99 63 c
97 61 a
100 64 d
101 65 e
110 6e n
97 61 a
--------------------------------------------------------
Puede examinar el fichero de clase entero compilando Strings.java y proporcionado el siguiente comando al analizador
de clases ParseClass descargable desde usuarios.tripod.es/JoseBotella/ :
java -classpath c:\ josebotella.parseclass.ParseClass Strings.class , el cual produce String.class.txt
Ejemplo 2
class UsandoStrings {
//Las clases que usan literales de cadena declaradas en otras clases
//contienen en su pool entradas CONTANT_Fieldref_Info para las variables
//de instancia (string1) y CONSTANT_String_Info para los campos con el modificador
//final
public static void main(String args[]) {
System.out.println(Strings.string3);
System.out.println(new Strings().string2);
System.out.println(new Strings().string1);
}
}
UsandoStrings.class.txt contiene en su pool una entrada CONSTANT_Fieldref_info para la tercera linea, y una
CONSTANT_String_Info para las dos primeras. Puede comprobar que las lineas primera y segunda del método main producen una entrada del tipo String en vez de Fieldref si comenta la tercera linea, compila y utiliza el analizador de clases.
Como ejemplo de entrada CONSTANT_Fielref_Info podemos presentar la producida por la linea tercera en main :
-------------------------------------------------------
9 9 tag (7) CONSTANT_Fieldref
0 0 class_index Strings
5 5
0 0 name_and_type_index string1 Ljava/lang/String;
25 19
--------------------------------------------------------
Esta entrada apunta a las entradas número 5 y 25 del pool. Estas otras a su vez, proporcionan la clase donde se declaró el campo, su nombre y descriptor:
--------------------------------------------------------
7 7 tag (5) CONSTANT_Class
0 0 name_index Strings
24 18
--------------------------------------------------------
--------------------------------------------------------
12 c tag (25) CONSTANT_NameAndType
0 0 name_index string1
34 22 "
0 0 descriptor_ index Ljava/lang/String;
35 23 #
--------------------------------------------------------
Donde puede ver que estas entradas apuntan a otras de número 24, 34 y 35. En el fichero UsandoStrings.class.txt se observa que tales entradas, del tipo CONSTANT_Utf8_Info, contienen respectivamente las cadenas: Strings, string1 y Ljava/lang/String;
Ejemplo 3
class NumericosFinal {
//Todos los literales constantes de tipos primitivos generan entradas
//CONSTANT_Fieldref en el pool de la clase que los declara.
//Ademas existen las apropiadas entradas de literales independientemente
//del valor de la constante
final byte byteF = (byte) 127 ;
final short shortF = (short) 3;
final int intF = 32767;
final long longF = 1L;
final float floatF = 1.5F;
final double doubleF = 1.55;
final char charF = 'B';
final boolean booleanF = true;
}
En el fichero NumericosFinal.class.txt puede ver que se han generado entradas CONSTANT_Fieldref para cada uno de los campos. Y que el valor de cada constante se ha almacenado en una entrada apropiada tal como se relaciona a continuación:
boolean, byte, short, char, int............CONSTANT_Integer_Info double..............................................CONSTANT_Double_Info float..................................................CONSTANT_Float_Info long..................................................CONSTANT_Lonf_Info String................................................CONSTANT_String_Info
A titulo de ejemplo la entrada para el campo booleanF es:
--------------------------------------------------------
3 3 tag (38) CONSTANT_Integer
0 0 bytes 1
0 0
0 0
1 1
--------------------------------------------------------
Ejemplo 4
Ahora una clase que utiliza la anterior:
class UsandoNumericosFinal {
//Las clases que usan literales numéricos constantes declarados
//en otras clases no contendrán en su pool entradas del tipo
//CONSTANT_Fieldref, sino de los tipos literales. O bien, ninguna entrada
//dependiendo del valor de la constante
public static void main(String args[]) {
NumericosFinal nf = new NumericosFinal();
System.out.println(nf.booleanF);
System.out.println(nf.byteF);
System.out.println(nf.intF);
System.out.println(nf.longF);
System.out.println(nf.shortF);
System.out.println(nf.floatF);
System.out.println(nf.doubleF);
System.out.println(nf.charF);
}
}
En su correspondiente fichero UsandoNumericosFinal.class.txt se observa que que no existen entradas del tipo Fieldref. Únicamente contamos con :
--------------------------------------------------------
4 4 tag (8) CONSTANT_Float
63 3f ? bytes 1.5
192 c0 À
0 0
0 0
--------------------------------------------------------
--------------------------------------------------------
6 6 tag (10) CONSTANT_Double
63 3f ? bytes 1.55
248 f8 ø
204 cc Ì
204 cc Ì
204 cc Ì
204 cc Ì
204 cc Ì
205 cd Í
--------------------------------------------------------
Estas entradas se refieren a los campos floatF y doubleF. Luego, ¿como emplea nuestro programa el resto de los campos?
La respuesta nos la proporciona el desensamblador javap de la forma siguiente: javap -c UsandoNumericosFinal
El comando anterior produce la salida:
Method void main(java.lang.String[])
0 new #2 (Class NumericosFinal)
3 dup
4 invokespecial #3 (Method NumericosFinal())
7 astore_1
8 getstatic #4 (Field java.io.PrintStream out)
11 iconst_1 //Se ocupa del boolean
12 invokevirtual #5 (Method void println(boolean))
15 getstatic #4 (Field java.io.PrintStream out)
18 bipush 127 //Se ocupa del byte
20 invokevirtual #6 (Method void println(int))
23 getstatic #4 (Field java.io.PrintStream out)
26 sipush 32767 //Se ocupa del int
29 invokevirtual #6 (Method void println(int))
32 getstatic #4 (Field java.io.PrintStream out)
35 lconst_1 //Se ocupa del long
36 invokevirtual #7 (Method void println(long))
39 getstatic #4 (Field java.io.PrintStream out)
42 iconst_3 //Se ocupa del short
43 invokevirtual #6 (Method void println(int))
46 getstatic #4 (Field java.io.PrintStream out)
49 ldc #8 (Real 1.5) //Se ocupa del float
51 invokevirtual #9 (Method void println(float))
54 getstatic #4 (Field java.io.PrintStream out)
57 ldc2_w #10 (Double 1.55) //Se ocupa del double
60 invokevirtual #12 (Method void println(double))
63 getstatic #4 (Field java.io.PrintStream out)
66 bipush 66 //Se ocupa del char
68 invokevirtual #13 (Method void println(char))
71 return
Como puede verse en el método main existen una serie de bytecodes que evitan la creación de entradas en el pool para
todos los campos excepto doubleF y floatF. Ello depende del valor que toman los campos.
Por ejemplo, la instrucción iconst_1 coloca un 1 en el pool de operandos para representar el valor true, y bipush 127, coloca dicho valor para el byte. Sin embargo, si pretendiésemos asignar 32768 a intF, no podría emplearse sipush 32768 para tal fin puesto que el bytecode sipush coloca en la pila de operandos un número dentro del rango short; como resultado la asignación de 32768 a un int debería hacerse con una entrada en el pool. Esta es la forma empleada en las lineas 57 y 49. ¿Por que?, porque no existe ninguna instrucción que sea capaz de asignar los valores 1.5 y 1.55 sin ayuda de una entrada en el pool. De haber querido asignar 1.0, el compilador hubiese empleado los bytecodes fconst_1 o dconst_1.
En definitiva, cuando el compilador juzga que un determinado bytecode puede evitar la referencia a una entrada en el pool, buscando una reducción en el numero de bytes o una ejecución mas rápida, utiliza dicha instrucción. Los posibles bytecodes empleados por el compilador a tal efecto son: bipush, sipush, iconst_(0,1,2,3,4,5,m1), dconst_(0,1), fconst_(0,1,2) y lconst_(0,1) . Puede encontrar una descripción de los bytecodes en el libro Inside the Java 2 Virtual Machine de Bill Venners.
En este programa el compilador no emplea entradas de literales cuando puede evitarlo. Solo en los casos doubleF y floatF hace referencia a entradas del tipo CONSTANT_Double y CONSTANT_Float existentes en su propio pool. Pues bien, estas entradas son copias de las existentes en el pool de la clase NumericosFinal.
Una última observación. En el fichero javapNumericosFinal.txt , obtenido con javap, también puede observarse la exclusión, siempre que sea posible, de las referencias al pool de la clase NumericosFinal. En esta clase, el constructor, a pesar de tener a su disposición entradas en el pool para cada una de las constantes, simplemente las evita.
Ejemplo 5
class NumericosFinalStatic {
//Las clases que declaran constantes estáticas no cuentan en su pool con entradas
//del tipo Fieldref sino de los tipos literales apropiados para contener el valor
final static byte byteSF = (byte) 12 ;
final static short shortSF = (short) 2;
final static int intSF = 32768;
final static long longSF = 5L;
final static float floatSF = 1.0F;
final static double doubleSF = 1.0;
final static char charSF = 'a';
final static boolean booleanSF = false;
}
En el fichero NumericosFinalStatic.class.txt no existe ninguna entrada CONSTANT_Fielref, pero si las correspondientes entradas CONSTANT_Integer etc.
Como todas los campos estáticos son constantes el método de inicialización de clase (<clinit>) no tiene código para inicializar los campos estáticos. De hecho, ni siquiera existe tal método en javapNumericosFinalStatic.txt
Ejemplo 6
class UsandoNumericosFinalStatic {
//De nuevo no existen entradas del tipo Fieldref, sino del tipo literal apropiado
//para el valor, siempre y cuando el valor no pueda ser computado por uno de los
//bytecodes vistos anteriormente
public static void main(String args[]) {
NumericosFinalStatic nfs = new NumericosFinalStatic();
System.out.println(nfs.booleanSF);
System.out.println(nfs.byteSF);
System.out.println(nfs.intSF);
System.out.println(nfs.longSF);
System.out.println(nfs.shortSF);
System.out.println(nfs.floatSF);
System.out.println(nfs.doubleSF);
System.out.println(nfs.charSF);
}
}
En el fichero UsandoNumericosFinalStatic.class.txt puede observarse la inexistencia de entradas Fieldref. Si figuran, en cambio, las dos entradas:
--------------------------------------------------------
3 3 tag (7) CONSTANT_Integer
0 0 bytes 32768
0 0
128 80 ?
0 0
--------------------------------------------------------
5 5 tag (8) CONSTANT_Long
0 0 bytes 5
0 0
0 0
0 0
0 0
0 0
0 0
5 5
--------------------------------------------------------
Las cuales se corresponden con los valores que no pueden ser expresados de otra forma, tal como se pone de manifiesto en
el fichero javapUsandoNumericosFinalStatic.txt
Ejemplo 7
class Numéricos {
//Cuando no son constantes las entrada de los tipos literales se generan únicamente
//si su valor no permite utilizar un bytecode que suponga un ahorro en bytes o
//mayor velocidad. En cambio se generan entradas Fieldref para todos los campos.
//Este comportamiento no depende del carácter estático o instancia del campo.
byte bipush = 127;
short bipushUnShort = 127;
short sipushUnShort = 128;
char bipusAChar = 'a';
char CONSTANT_Integer_2 = '\uFFFF' ;
int CONSTANT_Integer = 32768;
int sipush = 32767;
int bipushUnInteger = 127;
long CONSTANT_Long = 2;
long lconst_1 = 1;
float fconstant_2 = 2;
float CONSTANT_Float = 3;
double dconstant_1 = 1;
double CONSTANT_Double = 2;
static int CONSTANT_INTEGER_AStaticInt = 32769;
static int bipushUnIntegerStatic = 126;
}
En el fichero Numericos.class.txt se observa que los campos que generan entradas literales en el pool son nombrados empezando por CONSTANT. El resto poseen un nombre que recuerda la instrucción de la maquina virtual usada en lugar de una entrada en el pool. Todos los campos producen entradas CONSTANT_Fieldref.
En el fichero javapNumericos.txt se proporcionan estos bytecodes.
Ejemplo 8
class UsandoNumericos {
//Si una clase utiliza variables de otra, ya sean estáticas o no, su pool no contiene
//entradas de los tipos literales sino Fieldref
public static void main(String args[]) {
Numéricos n = new Numéricos();
System.out.println(n.CONSTANT_Long);
System.out.println(n.lconst_1);
System.out.println(Numericos.CONSTANT_INTEGER_AStaticInt);
System.out.println(Numericos.bipushUnIntegerStatic);
}
}
En el fichero UsandoNumericos.class.txt vemos cuatro entradas CONSTANT_Fieldref. Para los curiosos también
proporciono javapUsandoNumericos.txt donde los bytecodes getfield o getstatic acceden a los campos correspondientes.
Conclusiones de los ejemplos 1 a 8
Estos ejemplos han pretendido descubrir la relación entre el tipo de entrada producida en el pool y el tipo de campo.
Estas son las conclusiones:
a) Variables estáticas y de instancia .
La clase que declara variables contiene en su pool entradas Fielref para ellas. También contiene entradas del tipo Integer,
Long, Double, Float o String (literales) para expresar los valores asignados a las variables, siempre y cuando dicho valor no
pueda expresarse con un bytecode. La clase que usa variables definidas en otras clases contiene en su pool entradas del tipo
Fieldref para hacer referencia a las variables independientemente de su valor.
b) Constantes estáticas.
Las clases que declaran constantes estáticas cuentan en su pool con entradas de los tipos literales independientemente del
valor de la constante. La clase que usa constantes estáticas definidas en otras clases contendrá en su pool entradas de los
tipos literales siempre y cuando no exista un bytecode que pueda expresar el valor de la constante.
c) Constantes de instancia.
La clase que declara constantes no estáticas contiene en su pool entradas Fielref para todas ellas. Por lo demás, siguen el
mismo patrón que las constantes estáticas al generar entradas en el pool.
Ejemplo 9
En esta clase tenemos cinco ocurrencias del literal 90 double. Todas ellas generan una sola entrada CONSTANT_Double en
el pool. El fichero ExpresionNumerica.class.txt expone dicha entrada.
class ExpresionNumerica {
int expresión = (int) (Math.sin(90) + Math.sin(90) + Math.sin(90) );
double d1 = 90.0;
double d2 = 90.0;
}
Ejemplo 10
class Métodos {
//Los métodos declarados en una clase no originan entradas
//CONSTANT_Methodref en el pool de la clase
static int i = 3;
static int MetodoEstatico(int inti) { return inti; }
void MetodoInstancia() { }
}
En el fichero Metodos.class.txt no existe ninguna entrada Methodref para los métodos declarados en la clase. Solo la
invocación de los mismos las produciría. La única invocación de método es la del constructor de la clase padre:
--------------------------------------------------------
10 a tag (1) CONSTANT_Methodref
0 0 class_index java/lang/Object
4 4
0 0 name_and_type_index (init) ()V
17 11
--------------------------------------------------------
La entrada número 4 es del tipo CONSTANT_Class e indica la clase donde reside el método que se invocara. La número
17 es del tipo CONSTANT_NameAndType y apunta a su vez a otras dos del tipo CONSTANT_Utf8. Por último dichas
entradas contienen las cadenas con el nombre del método <init> y su descriptor ( )V.
La invocación del constructor padre se produce dentro del constructor de la propia clase Métodos como vemos en la salida
de javap -c Metodos :
class Métodos extends java.lang.Object {
static int i;
Métodos();
static int MetodoEstatico(int);
void MetodoInstancia();
static {};
}
Method Métodos()
0 aload_0
1 invokespecial #1 (Method java.lang.Object())
4 return
Method int MetodoEstatico(int)
0 iload_0
1 ireturn
Method void MetodoInstancia()
0 return
Method static {}
0 iconst_3
1 putstatic #2 (Field int i)
4 return
Vemos dos métodos creados automáticamente por el compilador. El constructor por defecto cuyo nombre es <init> y el
inicializador de clase <clinit>. Aunque javap elige mostrarnos otros nombres. La instrucción invokespecial #1 invoca el
método al cual la entrada número uno del pool referencia, es decir el constructor padre.
El método <clinit>, (aquí static), inicializa las variables estáticas y nunca es llamado por el programador, sino por la propia
maquina virtual . Esto determina que nunca existira una entrada en el pool para él.
Ejemplo 11
class UsandoMetodos {
public static void main(String[] args) {
Metodos.MetodoEstatico(1);
Métodos me = new Métodos();
me.MetodoInstancia();
}
}
El fichero UsandoMetodos.class.txt muestra dos entradas en el pool. Invocando una al constructor padre de esta clase, y la
otra al constructor de la clase Métodos producida por la instrucción new. También aparecen la entradas siguientes
apuntando simbólicamente a los métodos declarados en el ejemplo 10:
--------------------------------------------------------
10 a tag (2) CONSTANT_Methodref
0 0 class_index Métodos
3 3
0 0 name_and_type_index MetodoEstatico (I)I
17 11
--------------------------------------------------------
--------------------------------------------------------
10 a tag (5) CONSTANT_Methodref
0 0 class_index Métodos
3 3
0 0 name_and_type_index MetodoInstancia ()V
19 13
--------------------------------------------------------
En estos ejemplos la clase apuntada por class_index corresponde al tipo de la referencia sobre la cual se invoca el método.
En nuestro caso las referencias son me y Metodos.
Ejemplo 12
class SubMetodos extends Métodos implements Interfaz {
//El compilador hace que class_index de CONSTANT_Methodref o de
//CONSTANT_InterfaceMethodref apunte al tipo donde reside la declaración, del método
//que se esta invocando, mas cercana a la clase desde donde se invoca.
//El compilador busca el método en la clase o interfaz dada por la expresión sobre la
//que se produce la invocación del método.
//El compilador busca tanto métodos declarados en ese tipo como heredados de sus
//supertipos. class_index apunta al tipo padre si el método fue heredado y al hijo
//si el método fue sobrescrito.
public void MetodoInstancia() {
//sobrescribe el método padre
//que no haga nada no es relevante
}
public static void main(String[] args) {
new SubMetodos().test();
}
void test() {// class_index apuntara a...
super.MetodoInstancia(); //...Metodos 1
this.MetodoInstancia(); //...SubMetodos 5
this.MetodoEstatico(1); //...Metodos 9
MetodoInstancia(); //...SubMetodos 14
Métodos met = new SubMetodos();
met.MetodoInstancia(); //...Metodos 26
Interfaz inter = new SubMetodos();
inter.MetodoInstancia(); //...Interfaz 38
}
}
interface Interfaz {
void MetodoInstancia() ;
}
Deseamos comprobar el tipo apuntado por class_index en cada una de las invocaciones anteriores.
En el fichero SubMetodos.class.txt se pueden observar las siguientes entradas:
--------------------------------------------------------
10 a tag (5) CONSTANT_Methodref
0 0 class_index Métodos
9 9
0 0 name_and_type_index MetodoInstancia ()V
24 18
--------------------------------------------------------
10 a tag (6) CONSTANT_Methodref
0 0 class_index SubMetodos
2 2
0 0 name_and_type_index MetodoInstancia ()V
24 18
--------------------------------------------------------
10 a tag (7) CONSTANT_Methodref
0 0 class_index Métodos
9 9
0 0 name_and_type_index MetodoEstatico (I)I
25 19
--------------------------------------------------------
11 b tag (8) CONSTANT_InterfaceMethodref
0 0 class_index Interfaz
10 a
0 0 name_and_type_index MetodoInstancia ()V
24 18
--------------------------------------------------------
En la salida de javap -c Submetodos encontraremos las instrucciones de la maquina virtual que las utilizan:
Method void test()
0 aload_0
1 invokespecial #5 (Method void MetodoInstancia())
4 aload_0
5 invokevirtual #6 (Method void MetodoInstancia())
8 iconst_1
9 invokestatic #7 (Method int MetodoEstatico(int))
12 pop
13 aload_0
14 invokevirtual #6 (Method void MetodoInstancia())
17 new #2 (Class SubMetodos)
20 dup
21 invokespecial #3 (Method SubMetodos())
24 astore_1
25 aload_1
26 invokevirtual #5 (Method void MetodoInstancia())
29 new #2 (Class SubMetodos)
32 dup
33 invokespecial #3 (Method SubMetodos())
36 astore_2
37 aload_2
38 invokeinterface (args 1) #8 (InterfaceMethod void MetodoInstancia())
43 return
En el programa figura en cada invocación de método la linea en este listado donde se encuentra el bytecode que realiza la
llamada. Por ejemplo: super.MetodoInstancia() //Métodos 1 . En el listado de arriba la linea comenzando por 1 muestra
el bytecode invokeespecial #5 utilizado para realizar la llamada del método. El dígito 5 indica la entrada en el pool que
hace referencia al método llamado. Ademas en el comentario de la linea se indica que el class_index de la entrada ( #5 )
producida en el pool por dicha linea es del tipo Metodos.
En super.MetodoInstancia(); super tiene un tipo que es padre del que posee this: Métodos.
En this.MetodoInstancia(); this tiene el tipo SubMetodos.
En estos dos ejemplos, el tipo dado por la expresión sobre la cual se realiza la invocación del método declaró un método
cuyo descriptor coincide con el del método que se busca. Cumpliéndose ademas que dicho método es accesible según las
normas de los modificadores de acceso (public, protected, private), el tipo de la expresión es seleccionado por el compilador
para ser apuntado por class_index.
En this.MetodoEstatico() this, naturalmente, sigue teniendo el tipo SubMetodos pero el MetodoEstatico fue definido
an la clase padre y heredado, no sobrescrito. Luego class_index de la entrada en el pool número 7 apunta a la clase
Métodos. El compilador observó que la clase SubMetodos no definía el método que se esta invocando y buscó entre sus
supertipos. El carácter estático del método no varia el comportamiento descrito respecto de un método de instancia ni final.
En MetodoInstancia(); el tipo de la expresión es SubMetodos. Porque esta invocación es equivalente a usar this.
La clase SubMetodos si declaró ( sobrescribió ) el método invocado, luego esta es la clase apuntada por class_index.
En met.MetodoInstancia(); el tipo de met es la clase con la fue declarado. Esto es, Metodos.
En inter.MetodoInstancia(); el tipo de inter es Interfaz. En dicho interface fue declarado un método con el mismo
descriptor que aquel que se esta invocando, luego es dicho tipo el que aparece en class_index de la entrada
CONSTANT_InterfaceMethodref. Este tipo de entrada solo se genera cuando se invoca un método declarado en un
interface, pero únicamente si la invocación se realiza a través de una referencia cuyo tipo es un interface. En nuestro ejemplo
la referencia es inter.
Conclusiones de los ejemplos 10 a 12
Las entradas CONSTANT_InterfaceMethodref y CONSTANT_Methodref son generadas por la invocación de un método
en una clase. No los son por la declaración de métodos.
El tipo apuntado por class_index de una entrada en el pool del tipo CONSTANT_InterfaceMethodref o
CONSTANT_Methodref es el tipo de la referencia o expresión sobre la cual se produce la invocación del método,
siempre y cuando dicho tipo contenga una declaración del método invocado. En caso contrario el compilador buscara el
primer supertipo que lo declare.
Una entrada CONSTANT_InterfaceMethodref en el pool es creada únicamente por la invocación de un método declaradoen un interface, si esta invocación se produce sobre una referencia del tipo interface.