2011-02-20 4 views
24

Je travaille avec du code où un objet, "foo", crée un autre objet , "bar", et lui passe un Callable. Après ce foo retournera bar, et puis je veux que foo devienne inaccessible (ie: disponible pour garbage collection).Est-ce que les classes anonymes * * maintiennent toujours une référence à leur instance englobante?

Ma pensée initiale était de créer simplement le Callable anonymement. par exemple:

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

Il me est apparu que cela ne fonctionne pas vraiment comme on le souhaite, cependant, comme une classe interne conserve généralement une référence à son objet englobante. Je ne veux pas une référence à la classe englobante ici, car je veux que l'objet englobant soit collecté tandis que le Callable est toujours accessible.

D'autre part, détecter que l'instance englobante est ne fait référence à devrait être assez trivial, donc peut-être le compilateur Java est assez intelligent de ne pas inclure une référence dans ce cas. Alors ... une instance d'une classe interne anonyme conservera-t-elle une référence à son instance englobante même si elle n'utilise jamais réellement la référence d'instance englobante ?

Répondre

22

Oui, les instances de classes internes anonymes conservent une référence à leurs instances englobantes même si ces références sont jamais réellement utilisées. Ce code:

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

Lorsque compilé avec javac génère deux fichiers de classe, Outer.class et Outer$1.class. Démontage de celle-ci, la classe interne anonyme, avec javap -c rendement:

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 ligne putfield montre qu'une référence à l'instance d'enfermement est étant stockée dans le champ this$0 (de type Outer) par le constructeur même bien que ce champ ne soit jamais utilisé à nouveau. Ceci est regrettable si vous tentez de créer de petits objets à longue durée de vie potentiellement avec des classes internes anonymes, car ils conserveront l'instance englobante (potentiellement volumineuse). Une solution de contournement consiste à utiliser une instance d'une classe statique (ou une classe de niveau supérieur) à la place. C'est malheureusement plus bavard.

+0

Utilisez certainement une classe statique. Ce n'est pas ce IMO verbeux. –

+0

@deepc Vous devez ajouter des champs pour chaque paramètre, soit 1 ligne par paramètre, et un constructeur, qui ajoute 2 lignes plus 1 pour chaque paramètre. Donc, pour un Runnable simple à 3 arguments, c'est au moins 8 lignes de code de plus. La syntaxe de classe interne anonyme est déjà assez verbeuse par rapport à une syntaxe d'expression lambda correcte: une seule classe de méthode a 4 lignes de liste déroulante dont vous n'avez pas besoin avec des expressions lambda. Ainsi, une expression lambda à 1 ligne à 3 paramètres en Java devient 13 lignes de code. À quel point cela devrait-il être pour vous de le considérer comme «verbeux»? –

+0

vous avez raison avec votre ligne "statistiques". Peut-être que c'est un goût personnel. Mais après tout, la méthode 'call()' doit être là aussi. Et maintenant, nous avons assez de code pour justifier une classe séparée (pas nécessairement une classe de haut niveau comme vous le signalez). Pour moi, le code semble plus propre de cette façon. Encore plus si cette méthode est plus longue que quelques lignes. –

1

L'alternative statique (dans ce cas) n'est pas beaucoup plus grande (1 ligne):

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

BTW: si vous utilisez un Lambda Java8 il n'y aura pas de classe imbriquée généré. Cependant, je ne suis pas sûr que les objets CallSite qui sont passés dans ce cas contiennent une référence à l'instance externe (si ce n'est pas nécessaire).

5

Vous pouvez facilement transformer une classe anonyme imbriquée en une classe anonyme "statique" en introduisant une méthode statique dans votre 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

Ce n'est pas tout à fait une réponse à la question, mais c'est une bonne solution pour écrire une "classe anonyme statique" d'une manière beaucoup moins bavarde. Merci! –

Questions connexes