注意!非靜態內部類和非靜態方法的匿名類的this$0屬性

fairjm發表於2015-03-25

本文來自圖靈社群 fairjm@ituring 轉截請註明出處


別問我為什麼標題起得那麼長那麼奇怪....

在java中,非靜態內部類和非靜態方法中的匿名類會隱含有一個指向所在外部類例項的this$0屬性,如果不小心將這些類的例項當作返回值返回,那可能會導致原先的外部類例項無法被垃圾回收(這個引用是隱含的並被洩漏了出去)。

簡單的程式碼展示:

package tmp;

public class Test {

    static class InnerClass {
    }

    class NonStaticInnerClass {

    }

    public NonStaticInnerClass getInner() {
        return new NonStaticInnerClass();
    }

    public InnerClass getInnerStatic() {
        return new InnerClass();
    }

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

    public static Runnable getStaticAnonymous() {
        return new Runnable() {
            public void run() {
                System.out.println("hello");
            }
        };
    }

    public Runnable getLambda() {
        return () -> System.out.println("hello");
    }

    public static void main(String[] args) {
        Test t = new Test();
        NonStaticInnerClass inner = t.getInner(); //會有this$0屬性
        InnerClass staticInner = t.getInnerStatic();
        Runnable ano = t.getAnonymous(); //會有this$0屬性
        Runnable anoStatic = Test.getStaticAnonymous();
        Runnable lambda = t.getLambda();
    }
}

實際debug或者用反射可以看到和註釋一樣的結果。
對於getAnonymous()方法會產生一個class檔案(當然getStaticAnonymous()也會產生),名為Test$1.class。可以檢視下位元組碼的指令,就可以更清楚看到this$0了(NonStaticInnerClass也類似)

// Compiled from Test.java (version 1.8 : 52.0, super bit)
class tmp.Test$1 implements java.lang.Runnable {

  // Field descriptor #8 Ltmp/Test;
  final synthetic tmp.Test this$0;

  // Method descriptor #10 (Ltmp/Test;)V
  // Stack: 2, Locals: 2
  Test$1(tmp.Test arg0);
     0  aload_0 [this]
     1  aload_1 [arg0]
     2  putfield tmp.Test$1.this$0 : tmp.Test [12]
     5  aload_0 [this]
     6  invokespecial java.lang.Object() [14]
     9  return  

java的lambda可以取代原先的單方法匿名類,可以看到返回值使用lambda並不會導致外部類的例項外洩,看一下位元組碼可以發現使用了invokedynamic操作:

  public java.lang.Runnable getLambda();
    0  invokedynamic 0 run() : java.lang.Runnable [38]
    5  areturn    

關於這個操作 可以看: http://www.javaworld.com/article/2860079/scripting-jvm-languages/invokedynamic-101.html

為什麼我不解釋?因為我也不是很懂... ...就這樣啦~

相關文章