2010-05-10 15 views
20

En Java una clase interna anónima puede referirse a variables en el ámbito local que es:¿Cómo implementa Java los cierres de clase interna?

public class A { 
    public void method() { 
     final int i = 0; 

     doStuff(new Action() { 
      public void doAction() { 
       Console.printf(i); // or whatever 
      } 
     }); 
    } 
} 

Mi pregunta es ¿cómo se llevan a la práctica? ¿Cómo llega i a la implementación anónima interna doAction, y por qué tiene que ser final?

Respuesta

11

El compilador genera automáticamente un constructor para su clase interna anónima, y ​​pasa su variable local a este constructor.

El constructor guarda este valor en una variable de clase (un campo), también llamado i, que se usará dentro del "cierre".

¿Por qué tiene que ser definitiva? Así que vamos a explorar la situación en donde no está:

public class A { 
    public void method() { 
     int i = 0; // note: this is WRONG code 

     doStuff(new Action() { 
      public void doAction() { 
       Console.printf(i); // or whatever 
      } 
     }); 

     i = 4; // A 
     // B 
     i = 5; // C 
    } 
} 

En la situación A i el campo de Action también necesita ser cambiado, vamos a suponer que esto es posible: se necesita la referencia al objeto Action.

Supongamos que en la situación B esta instancia de Action es Basura-Recolectada.

Ahora en la situación C: necesita una instancia de Action para actualizar su variable de clase, pero el valor es GCed. Necesita "saber" que está GCed, pero eso es difícil.

Para simplificar la implementación de la máquina virtual, los diseñadores del lenguaje Java han dicho que debería ser definitiva, de modo que la máquina virtual no necesita una forma de verificar si un objeto se ha ido y garantizar que la variable no sea modificado, y que la VM o compilador no tiene que mantener referencia de todos los usos de la variable dentro de las clases internas anónimas y sus instancias.

+0

En realidad, la variable sintetizada que contiene una copia de la variable no se llama i. Dependiendo de la versión del compilador que esté usando, es "$ i" o "+ i". –

15

Las variables locales (obviamente) no se comparten entre diferentes métodos como method() y doAction() arriba. Pero dado que es final, nada "malo" podría suceder en este caso, por lo que el lenguaje aún lo permite. Sin embargo, el compilador necesita hacer algo inteligente sobre la situación. Vamos a echar un vistazo a lo javac produce:

$ javap -v "A\$1"   # A$1 is the anonymous Action-class. 
... 
final int val$i; // A field to store the i-value in. 

final A this$0;  // A reference to the "enclosing" A-object. 

A$1(A, int); // created constructor of the anonymous class 
    Code: 
    Stack=2, Locals=3, Args_size=3 
    0: aload_0 
    1: aload_1 
    2: putfield #1; //Field this$0:LA; 
    5: aload_0 
    6: iload_2 
    7: putfield #2; //Field val$i:I 
    10: aload_0 
    11: invokespecial #3; //Method java/lang/Object."<init>":()V 
    14: return 
    ... 
public void doAction(); 
    Code: 
    Stack=2, Locals=1, Args_size=1 
    0: getstatiC#4; //Field java/lang/System.out:Ljava/io/PrintStream; 
    3: aload_0 
    4: getfield #2; //Field val$i:I 
    7: invokevirtual #5; //Method java/io/PrintStream.println:(I)V 
    10: return 

En realidad, esto demuestra que

  • volvió la variable i en un campo,
  • creó un constructor de la clase anónima, que aceptó una mención al objeto A
  • al que posteriormente accedió en el método doAction().

(Una nota al margen:. Tenía que inicializar la variable a new java.util.Random().nextInt() para evitar que la optimización de distancia de una gran cantidad de código)


discusión similar aquí

method local innerclasses accessing the local variables of the method

+2

No tiene mucho (mucho) que ver con el enhebrado. Es simplemente un efecto secundario. Nice java disasm, por cierto: da una gran idea del compilador. – Pindatjuh

+0

Tienes razón. Voy a revisar. Gracias por el puntero. – aioobe

+0

@Pindatjuh, se actualizó el desastre ... se dio cuenta de que se optimizó una gran cantidad de código ya que el compilador se dio cuenta de que 'i' siempre era 0. – aioobe

3

La instancia de clase local (la clase anónima) debe mantener una copia separada de la variable, ya que puede superar la función. Para no tener la confusión de dos variables modificables con el mismo nombre en el mismo ámbito, la variable se fuerza a ser definitiva.

Ver Java Final - an enduring mystery para más detalles.

Cuestiones relacionadas