una que privada llamar instanciar estaticas ejemplos como clases clase anidadas anidada java reflection nested-class

java - que - ejemplos de clases anidadas c#



Problema con constructores de clase anidada (2)

Esta pregunta es sobre el comportamiento interesante de Java: produce un constructor adicional (no predeterminado) para las clases anidadas en algunas situaciones.

Esta pregunta también trata sobre la extraña clase anónima, que Java produce con ese extraño constructor.

Considera el siguiente código:

package a; import java.lang.reflect.Constructor; public class TestNested { class A { A() { } A(int a) { } } public static void main(String[] args) { Class<A> aClass = A.class; for (Constructor c : aClass.getDeclaredConstructors()) { System.out.println(c); } } }

Esto imprimirá:

a.TestNested$A(a.TestNested) a.TestNested$A(a.TestNested,int)

De acuerdo. A continuación, hagamos que el constructor A(int a) privado:

private A(int a) { }

Ejecuta el programa de nuevo. Recibir:

a.TestNested$A(a.TestNested) private a.TestNested$A(a.TestNested,int)

También está bien. Pero ahora, modifiquemos el método main() de esa forma (adición de una nueva instancia de creación de clase A ):

public static void main(String[] args) { Class<A> aClass = A.class; for (Constructor c : aClass.getDeclaredConstructors()) { System.out.println(c); } A a = new TestNested().new A(123); // new line of code }

Entonces la entrada se convierte en:

a.TestNested$A(a.TestNested) private a.TestNested$A(a.TestNested,int) a.TestNested$A(a.TestNested,int,a.TestNested$1)

¿Qué es: a.TestNested $ A (a.TestNested, int, a.TestNested $ 1) <<< --- ??

De acuerdo, volvamos a hacer localmente el paquete del constructor A(int a) :

A(int a) { }

Vuelva a ejecutar el programa (¡ no eliminamos la línea con la instancia de creación A !), La salida es como la primera vez:

a.TestNested$A(a.TestNested) a.TestNested$A(a.TestNested,int)

Preguntas:

1) ¿Cómo podría explicarse esto?

2) ¿Qué es este tercer constructor extraño?

ACTUALIZACIÓN: Investigación que se muestra a continuación.

1) Tratemos de llamar a este extraño constructor usando el reflejo de otra clase. No podremos hacer esto, porque no hay forma de crear una instancia de esa extraña clase TestNested$1 .

2) Ok. Vamos a hacer el truco. Vamos a agregar a la clase TestNested dicho campo estático:

public static Object object = new Object() { public void print() { System.out.println("sss"); } };

¿Bien? Ok, ahora podríamos llamar a este tercer constructor extraño de otra clase:

TestNested tn = new TestNested(); TestNested.A a = (TestNested.A)TestNested.A.class.getDeclaredConstructors()[2].newInstance(tn, 123, TestNested.object);

Lo siento, pero definitivamente no lo entiendo.

ACTUALIZACIÓN-2: Otras preguntas son:

3) ¿Por qué Java usa una clase interna especial anónima para un tipo de argumento para este tercer constructor sintético? ¿Por qué no solo el tipo de Object , del constructor con nombre especial?

4) ¿Qué Java podría usar la clase interna anónima ya definida para esos fines? ¿No es esto una especie de violación de la seguridad?


Antes que nada, gracias por esta interesante pregunta. Estaba tan intrigado que no pude resistirme a echar un vistazo al bytecode. Este es el bytecode de TestNested :

Compiled from "TestNested.java" public class a.TestNested { public a.TestNested(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: ldc_w #2 // class a/TestNested$A 3: astore_1 4: aload_1 5: invokevirtual #3 // Method java/lang/Class.getDeclaredConstructors:()[Ljava/lang/reflect/Constructor; 8: astore_2 9: aload_2 10: arraylength 11: istore_3 12: iconst_0 13: istore 4 15: iload 4 17: iload_3 18: if_icmpge 41 21: aload_2 22: iload 4 24: aaload 25: astore 5 27: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream; 30: aload 5 32: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 35: iinc 4, 1 38: goto 15 41: new #2 // class a/TestNested$A 44: dup 45: new #6 // class a/TestNested 48: dup 49: invokespecial #7 // Method "<init>":()V 52: dup 53: invokevirtual #8 // Method java/lang/Object.getClass:()Ljava/lang/Class; 56: pop 57: bipush 123 59: aconst_null 60: invokespecial #9 // Method a/TestNested$A."<init>":(La/TestNested;ILa/TestNested$1;)V 63: astore_2 64: return }

Como puede ver, el constructor a.TestNested$A(a.TestNested,int,a.TestNested$1) se invoca desde su método main . Además, null se pasa como el valor del parámetro a.TestNested$1 .

Así que echemos un vistazo a la misteriosa clase anónima a.TestNested$1 :

Compiled from "TestNested.java" class a.TestNested$1 { }

Extraño: esperaba que esta clase realmente hiciera algo. Para entenderlo, echemos un vistazo a los constructores en a.TestNested$A : class a.TestNested $ A {final a.TestNested this $ 0;

a.TestNested$A(a.TestNested); Code: 0: aload_0 1: aload_1 2: putfield #2 // Field this$0:La/TestNested; 5: aload_0 6: invokespecial #3 // Method java/lang/Object."<init>":()V 9: return private a.TestNested$A(a.TestNested, int); Code: 0: aload_0 1: aload_1 2: putfield #2 // Field this$0:La/TestNested; 5: aload_0 6: invokespecial #3 // Method java/lang/Object."<init>":()V 9: return a.TestNested$A(a.TestNested, int, a.TestNested$1); Code: 0: aload_0 1: aload_1 2: iload_2 3: invokespecial #1 // Method "<init>":(La/TestNested;I)V 6: return }

Al a.TestNested$A(a.TestNested, int, a.TestNested$1) constructor visible del paquete a.TestNested$A(a.TestNested, int, a.TestNested$1) , podemos ver que el tercer argumento se ignora.

Ahora podemos explicar el constructor y la clase interna anónima. Se requiere el constructor adicional para eludir la restricción de visibilidad en el constructor privado. Este constructor adicional simplemente delega en el constructor privado. Sin embargo, no puede tener la misma firma exacta que el constructor privado. Debido a esto, la clase interna anónima se agrega para proporcionar una firma única sin colisionar con otros posibles constructores sobrecargados, como un constructor con firma (int,int) o (int,Object) . Como esta clase interna anónima solo es necesaria para crear una firma única, no necesita ser instanciada y no necesita tener contenido.


El tercer constructor es un constructor sintético generado por el compilador, para permitir el acceso al constructor privado desde la clase externa. Esto se debe a que las clases internas (y el acceso de sus clases adjuntas a sus miembros privados) solo existen para el lenguaje Java y no para la JVM, por lo que el compilador tiene que cerrar la brecha detrás de las escenas.

La reflexión le dirá si un miembro es sintético:

for (Constructor c : aClass.getDeclaredConstructors()) { System.out.println(c + " " + c.isSynthetic()); }

Esto imprime:

a.TestNested$A(a.TestNested) false private a.TestNested$A(a.TestNested,int) false a.TestNested$A(a.TestNested,int,a.TestNested$1) true

Vea esta publicación para mayor discusión: ¿ Advertencia de Eclipse sobre el acceso sintético para clases anidadas estáticas privadas en Java?

EDITAR: curiosamente, el compilador de eclipse lo hace de forma diferente que javac. Al usar eclipse, agrega un argumento del tipo de la clase interna en sí:

a.TestNested$A(a.TestNested) false private a.TestNested$A(a.TestNested,int) false a.TestNested$A(a.TestNested,int,a.TestNested$A) true

Intenté tropezar exponiendo a ese constructor antes de tiempo:

class A { A() { } private A(int a) { } A(int a, A another) { } }

Se trató con esto simplemente agregando otro argumento al constructor sintético:

a.TestNested$A(a.TestNested) false private a.TestNested$A(a.TestNested,int) false a.TestNested$A(a.TestNested,int,a.TestNested$A) false a.TestNested$A(a.TestNested,int,a.TestNested$A,a.TestNested$A) true