2011-02-20 8 views
24

Estoy trabajando con un código en el que un objeto, "foo", crea otro objeto , "barra", y le pasa un Callable. Después de este foo devolverá bar, y luego quiero que foo sea inalcanzable (es decir, disponible para recolección de basura).Hacer clases anónimas * siempre * ¿mantener una referencia a su instancia adjunta?

Mi primer pensamiento fue crear el Callable de forma anónima. por ejemplo:

class Foo { 
    ... 

    public Bar createBar() { 
    final int arg1 = ... 
    final int arg2 = ... 
    final int arg3 = ... 
    return new Callable<Baz>() { 
     @Override 
     public Baz call() { 
     return new Baz(arg1, arg2, arg3); 
     } 
    }; 
    } 
} 

Se me ocurrió que esto no podría funcionar como se desee, sin embargo, como una clase interna normalmente mantiene una referencia a su objeto de cerramiento. No deseo una referencia a la clase adjunta aquí, porque quiero que el objeto adjunto sea recopilado mientras que el Callable aún se puede alcanzar.

Por otro lado, detectar que la instancia que encierra no se conoce realmente a debe ser bastante trivial, así que quizás el compilador Java es lo suficientemente inteligente como para que no incluya una referencia en ese caso.

Entonces ... ¿una instancia de una clase interna anónima se aferrará a una referencia a su instancia adjunta incluso si nunca utiliza realmente la referencia de instancia que contiene ?

Respuesta

22

Sí, las instancias de clases internas anónimas se aferran a una referencia a sus instancias adjuntas incluso si estas referencias son que nunca se utilizaron realmente. Este código:

public class Outer { 
    public Runnable getRunnable() { 
    return new Runnable() { 
     public void run() { 
     System.out.println("hello"); 
     } 
    }; 
    } 
} 

cuando se compila con javac genera dos archivos de clase, Outer.class y Outer$1.class. Desmontaje de este último, la clase interna anónima, con javap -c rendimientos:

Compiled from "Outer.java" 
class Outer$1 extends java.lang.Object implements java.lang.Runnable{ 
final Outer this$0; 

Outer$1(Outer); 
    Code: 
    0: aload_0 
    1: aload_1 
    2: putfield  #1; //Field this$0:LOuter; 
    5: aload_0 
    6: invokespecial #2; //Method java/lang/Object."<init>":()V 
    9: return 

public void run(); 
    Code: 
    0: getstatic  #3; //Field java/lang/System.out:Ljava/io/PrintStream; 
    3: ldc  #4; //String hello 
    5: invokevirtual #5; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 
    8: return 

} 

La línea putfield muestra que una referencia a la instancia que encierra se se almacena en el campo this$0 (de tipo Outer) por el constructor incluso aunque este campo nunca se usa nuevamente.

Esto es lamentable, si usted está tratando de crear pequeños objetos potencialmente de larga vida con las clases internas anónimas ya que va a aferrarse a la (potencialmente grande) que encierra ejemplo. Una solución alternativa es usar una instancia de una clase estática (o una clase de nivel superior) en su lugar. Esto es lamentablemente más detallado.

+0

Definitivamente el uso de una clase estática. No es esa OMI detallada. –

+0

@deepc Necesita agregar campos para cada parámetro, que es 1 línea por parámetro, y un constructor, que agrega 2 líneas más 1 para cada parámetro. Entonces, para un Runnable simple de 3 argumentos, hay al menos 8 líneas más de código. La sintaxis anónima de la clase interna ya es bastante detallada en comparación con una sintaxis de expresión lambda adecuada: una sola clase de método tiene 4 líneas de texto repetitivo que no necesitarías con expresiones lambda. Entonces, una expresión lambda de 3 líneas y 1 parámetro en Java se convierte en 13 líneas de código. ¿Qué tan grande debería ser para ti considerarlo "tan detallado"? –

+0

tiene razón con las "estadísticas" de su línea. Tal vez es un gusto personal. Pero después de todo, el método 'call()' tiene que estar allí también. Y ahora tenemos suficiente código para justificar una clase separada (no necesariamente una clase de nivel superior como usted señala). Para mí, el código se ve más limpio de esta manera. Aún más si ese método es más largo que unas pocas líneas. –

1

La alternativa estática (en este caso) no es mucho más grande (1 línea):

public class Outer { 
    static class InnerRunnable implements Runnable { 
     public void run() { 
     System.out.println("hello"); 
     } 
    } 
    public Runnable getRunnable() { 
    return new InnerRunnable(); 
    } 
} 

Por cierto: si se utiliza un lambda en el Java8 no habrá clase anidada generado. Sin embargo, no estoy seguro de si los objetos CallSite que se pasan en ese caso contienen una referencia a la instancia externa (si no es necesario).

5

Puede convertir fácilmente una clase anónima anidada en una clase anónima "estática" al introducir un método estático en su clase.

import java.util.ArrayList; 


public class TestGC { 
    public char[] mem = new char[5000000]; 
    public String str = "toto"; 

    public interface Node { 
     public void print(); 
    } 

    public Node createNestedNode() { 
     final String str = this.str; 
     return new Node() { 
      public void print() { 
       System.out.println(str); 
      } 
     }; 
    } 

    public static Node createStaticNode(TestGC test) { 
     final String str = test.str; 
     return new Node() { 
      public void print() { 
       System.out.println(str); 
      } 
     }; 
    } 

    public Node createStaticNode() { 
     return createStaticNode(this); 
    } 

    public static void main(String... args) throws InterruptedException { 
     ArrayList<Node> nodes = new ArrayList<Node>(); 
     for (int i=0; i<10; i++) { 
      // Try once with createNestedNode(), then createStaticNode() 
      nodes.add(new TestGC().createStaticNode()); 
      System.gc(); 
      //Thread.sleep(200); 
      System.out.printf("Total mem: %d Free mem: %d\n", Runtime.getRuntime().totalMemory(), Runtime.getRuntime().freeMemory()); 
     } 
     for (Node node : nodes) 
      node.print(); 
     nodes = null; 
     System.gc(); 
     //Thread.sleep(200); 
     System.out.printf("Total mem: %d Free mem: %d\n", Runtime.getRuntime().totalMemory(), Runtime.getRuntime().freeMemory()); 
    } 
} 
+0

Esta no es toda una respuesta a la pregunta, pero es una buena solución para escribir una "clase anónima estática" de una manera mucho menos detallada. ¡Gracias! –

Cuestiones relacionadas