java - obligatorio - ¿Todas las variables finales son capturadas por clases anónimas?
clases anonimas java definicion (4)
Estaba curioso y sorprendido por su declaración de que tanto (¿por qué el compilador haría tal cosa?), Que tuve que comprobarlo yo mismo. Así que hice un ejemplo simple como este.
public class test {
private static Object holder;
private void method1() {
final Object obj1 = new Object();
final Object obj2 = new Object();
holder = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println(obj1);
}
};
}
}
Y resultó con el siguiente bytecode for de method1
private method1()V
L0
LINENUMBER 8 L0
NEW java/lang/Object
DUP
INVOKESPECIAL java/lang/Object.<init> ()V
ASTORE 1
L1
LINENUMBER 9 L1
NEW java/lang/Object
DUP
INVOKESPECIAL java/lang/Object.<init> ()V
ASTORE 2
L2
LINENUMBER 10 L2
NEW test$1
DUP
ALOAD 0
ALOAD 1
INVOKESPECIAL test$1.<init> (Ltest;Ljava/lang/Object;)V
PUTSTATIC test.holder : Ljava/lang/Object;
Lo que significa:
- L0 - tienda primera final con idx 1 (ASTORE 1)
- L1: almacene la segunda final con idx 2 (esa no se usa en una clase anon) (ASTORE 2)
- L2: crear una nueva prueba $ 1 con argumets (ALOAD 0)
this
yobj1
(ALOAD 1)
Así que no tengo idea, ¿cómo llegó a la conclusión de que obj2
se pasa a una instancia de clase anónima, pero simplemente fue un error? IDK si es dependiente del compilador, pero en cuanto a lo que otros han declarado, no es imposible.
Pensé que sabía la respuesta a esto, pero no puedo encontrar ninguna confirmación después de aproximadamente una hora de búsqueda.
En este código:
public class Outer {
// other code
private void method1() {
final SomeObject obj1 = new SomeObject(...);
final SomeObject obj2 = new SomeObject(...);
someManager.registerCallback(new SomeCallbackClass() {
@Override
public void onEvent() {
System.out.println(obj1.getName());
}
});
}
}
Supongamos que registerCallback
guarda su parámetro en algún lugar, de modo que el objeto de la subclase anónima vivirá por un tiempo. Obviamente, este objeto debe mantener una referencia a obj1
para que onEvent
funcione si se llama.
Pero dado que el objeto no usa obj2
, ¿todavía mantiene una referencia a obj2
, de modo que obj2
no puede ser recolectado en la basura mientras el objeto vive? Tenía la impresión de que todas las variables y parámetros locales, visibles final
(o efectivamente finales) se capturaron y, por lo tanto, no se pudieron realizar GC''ed mientras el objeto estuviera vivo, pero no puedo encontrar nada que diga una forma o la otro.
¿Es dependiente de la implementación?
¿Hay alguna sección en el JLS que responda esto? No pude encontrar la respuesta allí.
La especificación del idioma tiene muy poco que decir acerca de cómo las clases anónimas deben capturar variables de su ámbito de inclusión.
La única sección especialmente relevante de la especificación de idioma que puedo encontrar es JLS Sec 8.1.3 :
Cualquier variable local, parámetro formal o parámetro de excepción usado pero no declarado en una clase interna debe ser declarado final o ser efectivamente final (§4.12.4), o se produce un error en tiempo de compilación cuando se intenta el uso.)
( Las clases anónimas son clases internas )
No especifica nada sobre qué variables debe capturar la clase anónima, o cómo se debe implementar esa captura.
Creo que es razonable deducir de esto que las implementaciones no necesitan capturar variables a las que no se hace referencia en la clase interna; Pero no dice que no puedan.
Sólo se captura obj1
.
Lógicamente , la clase anónima se implementa como una clase normal, algo como esto:
class Anonymous1 extends SomeCallbackClass {
private final Outer _outer;
private final SomeObject obj1;
Anonymous1(Outer _outer, SomeObject obj1) {
this._outer = _outer;
this.obj1 = obj1;
}
@Override
public void onEvent() {
System.out.println(this.obj1.getName());
}
});
Tenga en cuenta que una clase anónima es siempre una clase interna, por lo que siempre mantendrá una referencia a la clase externa, incluso si no la necesita. No sé si las versiones posteriores del compilador han optimizado eso, pero no lo creo. Es una posible causa de fugas de memoria.
Su uso se convierte en:
someManager.registerCallback(new Anonymous1(this, obj1));
Como puede ver, el valor de referencia de obj1
se copia (paso por valor).
Técnicamente, no hay razón para que obj1
sea final, ya sea declarado final
o efectivamente final (Java 8+), excepto que si no lo fue y usted cambia el valor, la copia no cambiará, lo que causará errores porque esperaba el valor Para cambiar, dado que la copia es una acción oculta. Para evitar la confusión del programador, decidieron que obj1
debe ser definitivo, de modo que nunca pueda confundirse con ese comportamiento.
obj2 será recolectado como basura ya que no tiene ninguna referencia a él. obj1 no se recolectará como basura mientras el evento esté activo, ya que incluso si creó una clase anónima, creó una referencia directa a obj1.
Lo único que hace la final es que no puede redefinir el valor, no protege el objeto del recolector de basura