Al compilar el código a continuación con el compilador de Java de OpenJDK 8, la llamada a foo() se realiza a través de invokespecial , pero cuando se usa OpenJDK 11, se emite una invokevirtual .
public class Invoke { public void call() { foo(); } private void foo() {} } Salida de javap -v -p cuando se usa javac 1.8.0_282:
public void call(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #2 // Method foo:()V 4: return Salida de javap -v -p cuando se usa javac 11.0.10:
public void call(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokevirtual #2 // Method foo:()V 4: return No entiendo por qué se usa invokevirtual aquí, ya que no puede haber una anulación de foo() .
Después de investigar un poco, parece que el propósito de invokevirtual en métodos privados es permitir que las clases anidadas llamen a métodos privados desde la clase externa. Así que probé el siguiente código:
public class Test{ public static void main(String[] args) { // Build a Derived such that Derived.getValue() // somewhat "exists". System.out.println(new Derived().foo()); } public static class Base { public int foo() { return getValue() + new Nested().getValueInNested(); } private int getValue() { return 24; } private class Nested { public int getValueInNested() { // This is getValue() from Base, but would // invokevirtual call the version from Derived? return getValue(); } } } public static class Derived extends Base { // Let's redefine getValue() to see if it is picked by the // invokevirtual from getValueInNested(). private int getValue() { return 100; } } } Al compilar este código con 11, podemos ver en la salida de javap que invokevirtual se usa tanto en foo() como en getValueInNested() :
public int foo(); descriptor: ()I flags: (0x0001) ACC_PUBLIC Code: stack=4, locals=1, args_size=1 0: aload_0 // ** HERE ** 1: invokevirtual #2 // Method getValue:()I 4: new #3 // class Test$Base$Nested 7: dup 8: aload_0 9: invokespecial #4 // Method Test$Base$Nested."<init>":(LTest$Base;)V 12: invokevirtual #5 // Method Test$Base$Nested.getValueInNested:()I 15: iadd 16: ireturn public int getValueInNested(); descriptor: ()I flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: getfield #1 // Field this$0:LTest$Base; // ** HERE ** 4: invokevirtual #3 // Method Test$Base.getValue:()I 7: ireturnTodo esto es un poco confuso y plantea algunas preguntas:
invokevirtual se usa para llamar a métodos privados? ¿Hay algún caso de uso en el que reemplazarlo por un objeto de invokespecial no sería equivalente?getValue() en Nested.getValueInNested() no elige el método de Derived ya que se llama a través invokevirtual ?Esto se hizo como parte de https://openjdk.java.net/jeps/181 : control de acceso basado en nido, para que la JVM pueda permitir el acceso a métodos privados desde clases anidadas.
Antes de ese cambio, el compilador tendría que generar un método sintético protegido por paquete en la clase Base , que invoca la clase anidada. Ese método sintético a su vez llamaría al método privado en la clase Base . La característica en Java 11 mejora la JVM para permitir eso sin que el compilador tenga que generar un método sintético.
Con respecto al punto sobre si invokevirtual llamará al método en la clase Derived , la respuesta es no. El método privado todavía no está sujeto a la selección de métodos de la clase de tiempo de ejecución (esto nunca ha cambiado):
Durante la ejecución de una instrucción de
invokeinterfacede interfaz oinvokevirtual, se selecciona un método con respecto a (i) el tipo de tiempo de ejecución del objeto en la pila y (ii) un método que la instrucción resolvió previamente. Las reglas para seleccionar un método con respecto a una clase o interfaz C y un método m R son las siguientes:
- Si m R está marcado como
ACC_PRIVATE, entonces es el método seleccionado.
EDITAR:
Basado en el comentario "¿Sería válido seguir usando invocar especial si el método privado se llama desde la clase propietaria del método y usar invocar virtual si se llama desde una clase anidada?"
Como mencionó Holger, sí, es válido, pero según el JEP , supongo que se tomó la decisión de cambiar a invokevirtual por simplicidad (aunque no puedo confirmar esto, es solo una suposición):
Con el cambio en las reglas de acceso, y con los ajustes adecuados a las reglas de códigos de bytes, podemos permitir reglas simplificadas para generar códigos de bytes de invocación:
- invocar especial para constructores anidados privados,
- invoquevirtual para métodos de instancia de nestmate privados sin interfaz,
- invocar interfaz para interfaz privada, métodos de instancia de nestmate; y
- invocar estático para nestmate privado, métodos estáticos
Otra nota interesante de JDK-8197445: Implementación de JEP 181: control de acceso basado en nidos :
Tradicionalmente,
invokespecialse usa para invocar miembrosprivate, aunqueinvokevirtualtambién tiene esta capacidad. En lugar de perturbar las reglas complejas sobre los supertipos impuestas porinvokespecial, requerimos invocaciones de métodosprivateen una clase diferente para usarinvokevirtual.
Pensé que sería apropiado agregar más información a la respuesta ya proporcionada y aceptada, aunque no es estrictamente necesario, puede ayudar a ampliar la comprensión, por lo tanto, está dentro del mejor interés de los usuarios de SO.
En versiones anteriores, antes de Java 11, como ya señaló @ma en la respuesta aceptada, el compilador necesitaría crear métodos de puente para permitir que las clases accedan a los miembros privados de cada una en tales condiciones. Estos métodos puente que amplían la accesibilidad se llaman en el contexto de ejecución, donde el compilador inserta código en un programa en ejecución.
Hacer esto aumenta el tamaño de las aplicaciones implementadas y aumenta la complejidad, además de que hace que sea más difícil comprender lo que sucede detrás de escena.
Java 11 introdujo el concepto de control de acceso basado en nido . Junto con la noción de compañeros de anidamiento y las reglas de acceso asociadas dentro de la JVM, esto permite que las clases y las interfaces se aniden entre sí.
Los tipos anidados pueden ser campos privados , métodos y constructores.
Usando la API de reflexión actualizada, ahora puede consultar información sobre la funcionalidad de control de acceso basada en nido.
Algunas bondades nuevas en Java 11
El método getNestHost() se usa para obtener el nombre del host del nido y el método isNestmateOf() se puede usar para verificar si una clase es un compañero de nido . Además, el método getNestMembers() devuelve una matriz de miembros del nido.
Aquí hay un enlace a un ejemplo genérico, cortesía de Baeldung.com, control de acceso basado en Nest que hace que los beneficios se destaquen bastante bien en mi humilde opinión.
Tenga en cuenta que no hay un método puente generado por el compilador en el código desensamblado. Además, la clase Inner ahora puede realizar una llamada directa al método outsidePrivate() en el ejemplo vinculado anteriormente.