java - tipos - ¿Por qué una clase interna anónima no contiene nada generado de este código?
que es clase anonima java (5)
package com.test;
public class OuterClass {
public class InnerClass {
public class InnerInnerClass {
}
}
public class InnerClass2 {
}
//this class should not exist in OuterClass after dummifying
private class PrivateInnerClass {
private String getString() {
return "hello PrivateInnerClass";
}
}
public String getStringFromPrivateInner() {
return new PrivateInnerClass().getString();
}
}
Cuando se ejecuta a través de javac
en la línea de comandos con Sun JVM 1.6.0_20
, este código produce 6 archivos .class:
OuterClass.class
OuterClass $ 1.class
OuterClass $ InnerClass.class
OuterClass $ InnerClass2.class
OuterClass $ InnerClass $ InnerInnerClass.class
OuterClass $ PrivateInnerClass.class
Cuando se ejecuta a través de JDT en eclipse, produce solo 5 clases.
OuterClass.class
OuterClass $ 1.class
OuterClass $ InnerClass.class
OuterClass $ InnerClass2.class
OuterClass $ InnerClass $ InnerInnerClass.class
OuterClass $ PrivateInnerClass.class
Cuando descompilado, OuterClass$1.class
no contiene nada. ¿De dónde viene esta clase extra y por qué se crea?
Basándome en la respuesta de los polilenetrófilos, supongo que esta misteriosa clase impide que cualquier otra persona (es decir, fuera de OuterClass
) instancia una OuterClass$PrivateInnerClass
, porque no tienen acceso a OuterClass$1
.
Después de buscar, encontré este enlace. http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6378717
El comentario hace referencia al código fuente que está disponible en un enlace determinado.
Esto no es un error.
El compilador está tratando de resolver un problema de acceso. Como la clase interna Test.Request es privada, su constructor es privado. Esto puede verse si usa -private to javap:
$ javap -private Test / $ Request compilado de la clase final "Test.java" Test $ Request extiende java.lang.Object {final Prueba esto $ 0; prueba privada $ solicitud (prueba); Prueba $ Solicitud (prueba, prueba $ 1); }
Sin embargo, la JVM no permitirá la subclase anónima de acceso Coucou (Prueba $ 1) a este constructor privado. Esta es una diferencia fundamental entre JVM y el lenguaje de programación Java cuando se trata de clases anidadas. El lenguaje permite que las clases anidadas accedan a los miembros privados de la clase adjunta.
Originalmente, cuando se agregaban clases anidadas al idioma, la solución a este problema era hacer que el paquete constructor fuera privado y se vería así:
$ javap -private Test / $ Request compilado de la clase final "Test.java" Test $ Request extiende java.lang.Object {final Prueba esto $ 0; Prueba $ Solicitud (Prueba); }
Sin embargo, esto puede conducir fácilmente a problemas donde puede obtener acceso al constructor cuando no debería. Para resolver este problema, se inventó la solución actual. El constructor "real" se mantendrá privado:
private Test$Request(Test);
Sin embargo, otras clases anidadas deben poder llamar a este constructor. Por lo tanto, se debe proporcionar un constructor de acceso. Sin embargo, este constructor de acceso debe ser diferente del constructor "real". Para resolver este problema, el compilador agrega un parámetro extra al constructor de acceso. El tipo de este parámetro adicional debe ser algo único que no entre en conflicto con cualquier cosa que el usuario haya escrito. Entonces, una solución obvia es agregar una clase anónima y usarla como el tipo del segundo parámetro:
Test$Request(Test, Test$1);
Sin embargo, el compilador es inteligente y reutiliza cualquier clase anónima, si existe. Si cambia el ejemplo para no incluir una clase anónima, verá que el compilador creará uno:
clase de resumen público Prueba {clase final privada Solicitud {} clase final privada OtherRequest {Request test () {return new Request (); }}}
Si no hay acceso al constructor privado, el compilador no necesita generar ningún constructor de acceso que explique el comportamiento de este ejemplo:
clase de resumen público Prueba {clase privada final Solicitud {}}
Estoy usando un fragmento más pequeño de Polygenelubricants.
Recuerde que no hay concepto de clases anidadas en el bytecode; el bytecode es, sin embargo, consciente de los modificadores de acceso. El problema que el compilador intenta eludir aquí es que el método instantiate()
necesita crear una nueva instancia de PrivateInnerClass
. Sin embargo, OuterClass
no tiene acceso al constructor de OuterClass$PrivateInnerClass
( OuterClass$PrivateInnerClass
se generará como una clase protegida por paquete sin un constructor público).
Entonces, ¿qué puede hacer el compilador? La solución obvia es cambiar PrivateInnerClass
para tener un constructor protegido por paquete. El problema aquí es que esto permitirá que cualquier otro código que interactúe con la clase cree una nueva instancia de PrivateInnerClass
, ¡aunque se haya declarado explícitamente como privado!
Para intentar evitar eso, el compilador javac está haciendo un pequeño truco: en lugar de hacer que el constructor habitual de PrivateInnerClass
visible desde otras clases, lo deja como oculto (en realidad no lo define en absoluto, pero eso es lo mismo desde afuera ) En cambio, crea un nuevo constructor que recibe un parámetro adicional del tipo especial OuterClass$1
.
Ahora, si instantiate()
, llama a ese nuevo constructor. De hecho, envía null
como el 2 ° parámetro (del tipo OuterClass$1
) - ese parámetro solo se usa para especificar que este constructor es el que se debe llamar.
Entonces, ¿por qué crear un nuevo tipo para el segundo parámetro? ¿Por qué no usar, por ejemplo, Object
? ¡Solo se usa para diferenciarlo del constructor regular y null
se pasa de todos modos! Y la respuesta es que como OuterClass$1
es privado para OuterClass, un compilador legal nunca permitirá que el usuario invoque el OuterClass$PrivateInnerClass
especial OuterClass$PrivateInnerClass
, ya que uno de los tipos de parámetros necesarios, OuterClass$1
, está oculto.
Supongo que el compilador de JDT usa otra técnica para resolver el mismo problema.
No tengo la respuesta, pero puedo confirmarlo y reducir el fragmento a lo siguiente:
public class OuterClass {
private class PrivateInnerClass {
}
public void instantiate() {
new PrivateInnerClass();
}
}
Esto crea OuterClass$1.class
Compiled from "OuterClass.java"
class OuterClass$1 extends java.lang.Object{
}
Y aquí está javap -c
para OuterClass.class
:
Compiled from "OuterClass.java"
public class OuterClass extends java.lang.Object{
public OuterClass();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
public void instantiate();
Code:
0: new #2; //class OuterClass$PrivateInnerClass
3: dup
4: aload_0
5: aconst_null
6: invokespecial #3; //Method OuterClass$PrivateInnerClass."<init>":
//(LOuterClass;LOuterClass$1;)V
9: pop
10: return
}
Y para OuterClass$PrivateInnerClass
:
Compiled from "OuterClass.java"
class OuterClass$PrivateInnerClass extends java.lang.Object{
final OuterClass this$0;
OuterClass$PrivateInnerClass(OuterClass, OuterClass$1);
Code:
0: aload_0
1: aload_1
2: invokespecial #1; //Method "<init>":(LOuterClass;)V
5: return
}
Como puede ver, el constructor sintetizado toma un argumento de OuterClass$1
.
Entonces javac
crea el constructor predeterminado para tomar un argumento extra, del tipo $1
, y el valor de ese argumento predeterminado es 5: aconst_null
.
Descubrí que $1
no se crea si cualquiera de los siguientes es verdadero:
- Usted hace de
public class PrivateInnerClass
- Usted declara un constructor
PrivateInnerClass
paraPrivateInnerClass
- O no llamas al
new
- Probablemente otras cosas (por ejemplo, anidado
static
, etc.).
Posiblemente relacionado
- Error ID: 4295934: la compilación de una clase interna privada crea un archivo de clase anónimo en el directorio incorrecto
Cree la siguiente fuente en un directorio llamado test:
package test; public class testClass { private class Inner { } public testClass() { Inner in = new Inner(); } }
Compila el archivo del directorio padre
javac test/testClass.java
Tenga en cuenta que el archivo
testClass$1.class
se crea en el directorio actual. No estoy seguro de por qué se crea este archivo, ya que también hay unatest/testClass$Inner.class
creada también.EVALUACIÓN
El archivo
testClass$1.class
es para una clase ficticia necesaria para un "constructor de acceso" para el constructor privado de la clase interna privadatestClass$Inner
. El desmontaje muestra que el nombre completo de esta clase está anotado correctamente, por lo que no está claro por qué el archivo de clase termina en el directorio incorrecto.
Un punto más: si OuterClass$1
ya ha sido declarado por el usuario, OuterClass$PrivateInnerClass
lo tendrá como argumento constructor de todos modos:
public class OuterClass {
...
public String getStringFromPrivateInner() {
PrivateInnerClass c = new PrivateInnerClass();
Object o = new Object() {};
return null;
}
}
-
public java.lang.String getStringFromPrivateInner(); Code: 0: new #2; //class OuterClass$PrivateInnerClass 3: dup 4: aload_0 5: aconst_null 6: invokespecial #3; //Method OuterClass$PrivateInnerClass."": (LOuterClass;LOuterClass$1;)V 9: astore_1 10: new #4; //class OuterClass$1 13: dup 14: aload_0 15: invokespecial #5; //Method OuterClass$1."":(LOuterClass;)V 18: astore_2 19: aconst_null 20: areturn