2011-02-20 5 views
24

オブジェクト "foo"が別の オブジェクト "bar"を作成し、それをCallableに渡しているコードで作業しています。このfooが バーを返した後、fooが到達不能になるようにします(つまり、 ガーベジコレクションで利用可能)。匿名のクラスを行う*常に*それらの囲むインスタンスへの参照を保持しますか?

私の最初の考えは、Callableを匿名で作成することでした。例えば:

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

必要に応じて、これは実際に動作しない場合があります、しかし、内部クラスとして は通常、その囲んでいるオブジェクトへの参照を保持していることに私に起こりました。 Callableが依然として到達可能である間に、囲みオブジェクトを収集したいので、ここで囲んでいるクラスへの参照は欲しくないので、 を収集します。一方

、 は親インスタンスが実際に 呼ばれないことを検出することは、かなり些細なことなので、おそらくJavaコンパイラは、その場合の参照が含まれていないのに十分な スマートです必要があります。

匿名の内部クラスのインスタンスは、実際に のインスタンス参照を使用することがない場合でも、囲むインスタンスへの参照を に保持しますか?

答えて

22

はい、匿名の内部クラスのインスタンスは、 が実際に使用されていなくても、囲むインスタンスへの参照を に保持します。このコード:

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

javacでコンパイルされた2つのクラスファイル、Outer.classOuter$1.classを生成します。 javap -c収率で後者の、匿名の内部クラス、 を分解:

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 

} 

putfield線が囲むインスタンスへの参照を コンストラクタさえによりthis$0(タイプOuterの)フィールドに格納されていることを示していますこのフィールドは決して再び使用されません。

潜在的に匿名の内部クラスを持つオブジェクトを (潜在的に大きい)のインスタンスに保持するように作成しようとすると、これは残念です。回避策は、代わりに静的クラス(または最上位クラス)のインスタンスを使用することです。これは残念ながらもっと冗長です。ところで

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

+0

間違いなく静的クラスを使用します。それはその冗長なIMOではありません。 –

+0

@deepcパラメータごとに1行のフィールドを追加する必要があります。また、各パラメータに2行と1を加えたコンストラクタも追加する必要があります。そのため、単純に3引数のRunnableの場合、少なくとも8行以上のコードが必要です。匿名の内部クラス構文は、適切なラムダ式構文と比べて既にかなり冗長です.1つのメソッドクラスには、ラムダ式では必要ない定型文の4行があります。したがって、Javaでの3パラメータの1行ラムダ式は13行のコードになります。あなたはそれを「冗長」と見なす必要がありますか? –

+0

あなたはあなたのライン "統計"に合っています。たぶんそれは個人的な味です。しかし、すべての後に 'call()'メソッドもそこになければなりません。そして今、別のクラスを正当化するのに十分なコードがあります(あなたが指摘するように必ずしもトップレベルのクラスである必要はありません)。私には、コードはこのようにきれいに見えます。その方法が数行よりも長い場合はさらにそうです。 –

1

(この場合)静的代替は、(1本のライン)はるかに大きいではありませんあなたはJava8でラムダを使用する場合に生成入れ子になったクラスは存在しません。しかし、その場合に渡されるCallSiteオブジェクトが外部インスタンスへの参照を保持するかどうかはわかりません(必要でない場合)。

5

クラスに静的メソッドを導入することで、ネストされた匿名クラスを「静的な」匿名クラスに簡単に切り替えることができます。

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

これは疑問の答えではありませんが、 "静的な匿名クラス"を書く方法の良い解決策です。ありがとう! –