2011-02-20 10 views
24

Sto lavorando con un codice in cui un oggetto, "pippo", sta creando un altro oggetto , "barra", e lo passa a Callable. Dopo questo foo restituirà la barra , e poi voglio che foo diventi irraggiungibile (es .: disponibile per la garbage collection ).Le classi anonime * sempre * mantengono un riferimento alla loro istanza allegata?

Il mio primo pensiero era di creare lo Callable in modo anonimo. es:

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); 
     } 
    }; 
    } 
} 

Pensai che questo non potrebbe effettivamente funzionare come desiderato, tuttavia, come una classe interna tipicamente mantiene un riferimento al suo oggetto contenitore. Non desidero un riferimento alla classe di inclusione qui, perché voglio che l'oggetto che racchiude sia raccolto mentre lo Callable è ancora raggiungibile.

D'altra parte, rilevare che l'istanza che racchiude non è mai in realtà di cui dovrebbe essere abbastanza banale, quindi forse il compilatore Java è abbastanza intelligente di non includere un riferimento in questo caso.

Quindi ... un'istanza di una classe interna anonima si aggrappa a un riferimento all'istanza che lo include anche se non utilizza mai effettivamente il riferimento di istanza che include?

risposta

22

Sì, le istanze di classi interne anonime tenere su di un riferimento alle loro istanze di cinta, anche se questi riferimenti sono mai effettivamente utilizzati. Questo codice:

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

quando si compila con javac genera due file di classe, Outer.class e Outer$1.class. Smontare quest'ultimo, la classe interna anonima, con javap -c rese:

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 linea putfield mostra che un riferimento all'istanza racchiude viene essendo memorizzato nel campo this$0 (di tipo Outer) dal costruttore anche sebbene questo campo non venga mai più utilizzato.

Questo è un peccato se si sta tentando di creare piccole potenzialmente oggetti longevi con le classi interne anonime come faranno tenere sul (potenzialmente elevato) istanza allegando. Una soluzione alternativa è utilizzare un'istanza di una classe statica (o una classe di livello superiore). Questo è sfortunatamente più prolisso.

+0

Utilizzare decisamente una classe statica. Non è quel verboso IMO. –

+0

@deepc È necessario aggiungere campi per ogni parametro, ovvero 1 riga per parametro e un costruttore, che aggiunge 2 righe più 1 per ogni parametro. Quindi per un semplice Runnable a 3 argomenti ci sono almeno altre 8 righe di codice. La sintassi anonima della classe interna è già piuttosto prolissa rispetto a una sintassi di espressione lambda corretta: una singola classe di metodo ha 4 righe di testo non necessarie con espressioni lambda. Quindi un'espressione lambda a 3 parametri a 1 riga in Java diventa 13 righe di codice. Quanto dovrebbe essere grande per te considerarlo "quel verboso"? –

+0

hai ragione con la tua linea "statistiche". Forse è un gusto personale. Ma dopotutto deve esserci anche il metodo 'call()'. E ora abbiamo abbastanza codice per giustificare una classe separata (non necessariamente una classe di livello superiore come fai notare). Per me il codice sembra più pulito in questo modo. Ancor di più se quel metodo è più lungo di poche righe. –

1

L'alternativa statica (in questo caso) non è molto più grande (1 riga):

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

BTW: se si utilizza un Lambda in Java8 non vi sarà alcuna classe annidata generata. Tuttavia non sono sicuro che gli oggetti CallSite che vengono passati in quel caso mantengano un riferimento all'istanza esterna (se non necessario).

5

È possibile trasformare facilmente una classe anonima nidificata in una classe anonima "statica" introducendo un metodo statico nella classe.

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

Questa non è una risposta alla domanda, ma è una buona soluzione per scrivere una "classe anonima statica" in un modo molto meno dettagliato. Grazie! –

Problemi correlati