天天看點

java使用匿名内部類通路變量_匿名内部類可以通路的變量---靜态成員變量和final修飾的局部變量...

在學習多線程的時候用到了匿名内部類,匿名内部類可以通路static靜态成員變量或者final修飾的局部變量。

匿名内部類在編譯之後會生成class檔案,比如Test内的第一個匿名内部類編譯之後就是Test$1.class;

匿名内部類中通路的final修飾的局部變量在生成Test$1.class之後會作為構造方法的參數傳入class中;如果匿名内部類通路的是另一個類的靜态成員變量則直接通路,不會作為構造方法的參數。

1.通路final修飾的局部變量

局部變量需要是final修飾,如果通路方法參數,方法的參數也需要是final修飾的

packagecn.xm.exam.test;importjava.util.ArrayList;importjava.util.List;public classTest1 {public static voidmain(String[] args) {final List list = new ArrayList<>();

list.add("111");new Thread(newRunnable() {

@Overridepublic voidrun() {

System.out.println(list+ ",threadName->" +Thread.currentThread().getName());

}

}).start();

test1("xxx");

}public static void test1(finalObject object) {new Thread(newRunnable() {

@Overridepublic voidrun() {

System.out.println(object+ ",threadName->" +Thread.currentThread().getName());

}

}).start();

}

}

結果:

[111],threadName->Thread-0

xxx,threadName->Thread-1

需要用final修飾的原因:

内部類裡面使用外部類的局部變量時,其實就是内部類的對象在使用它,内部類對象生命周期中都可能調用它,而内部類試圖通路外部方法中的局部變量時,外部方法的局部變量很可能已經不存在了,那麼就得延續其生命,拷貝到内部類中,而拷貝會帶來不一緻性,進而需要使用final聲明保證一緻性。說白了,内部類會自動拷貝外部變量的引用,為了避免:1. 外部方法修改引用,而導緻内部類得到的引用值不一緻 2.内部類修改引用,而導緻外部方法的參數值在修改前和修改後不一緻。于是就用 final 來讓該引用不可改變

Java為了避免資料不同步的問題,做出了匿名内部類隻可以通路final的局部變量的限制。

反編譯檢視源碼:(一個Java檔案反編譯出來三個class檔案,也就是匿名内部類也被編譯為class)

C:\Users\liqiang\Desktop\建立檔案夾>ls'Test1$1.class' 'Test1$2.class' Test1.class Test1.java

(1)檢視Test1$1.class(可以了解為Test1的第一個内部類,實際是将内部通路的final修飾的變量作為參數傳入此類的構造方法):

java使用匿名内部類通路變量_匿名内部類可以通路的變量---靜态成員變量和final修飾的局部變量...

javap反彙編檢視:

C:\Users\liqiang\Desktop\建立檔案夾>javap -c Test1$1.classCompiled from"Test1.java"

final class cn.xm.exam.test.Test1$1 implementsjava.lang.Runnable {finaljava.util.List val$list;

cn.xm.exam.test.Test1$1(java.util.List);

Code:0: aload_01: aload_12: putfield #1 //Field val$list:Ljava/util/List;

5: aload_06: invokespecial #2 //Method java/lang/Object."":()V

9: return

public voidrun();

Code:0: getstatic #3 //Field java/lang/System.out:Ljava/io/PrintStream;

3: new #4 //class java/lang/StringBuilder

6: dup7: invokespecial #5 //Method java/lang/StringBuilder."":()V

10: aload_011: getfield #1 //Field val$list:Ljava/util/List;

14: invokevirtual #6 //Method java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/Stri

ngBuilder;17: ldc #7 //String ,threadName->

19: invokevirtual #8 //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/Stri

ngBuilder;22: invokestatic #9 //Method java/lang/Thread.currentThread:()Ljava/lang/Thread;

25: invokevirtual #10 //Method java/lang/Thread.getName:()Ljava/lang/String;

28: invokevirtual #8 //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/Stri

ngBuilder;31: invokevirtual #11 //Method java/lang/StringBuilder.toString:()Ljava/lang/String;

34: invokevirtual #12 //Method java/io/PrintStream.println:(Ljava/lang/String;)V

37: return}

(2)檢視Test1$2.class(可以了解為Test1的第二個内部類,實際是将内部通路的final修飾的變量作為參數傳入此類的構造方法):

java使用匿名内部類通路變量_匿名内部類可以通路的變量---靜态成員變量和final修飾的局部變量...

(3)檢視Test1.class

反編譯看不出來,直接反彙編檢視:可以看出是建立了對應的匿名内部類,并且将參數摻入構造方法中(main方法建立Test1$1類執行個體,test1方法建立Test2$2類執行個體)

Compiled from "Test1.java"

public classcn.xm.exam.test.Test1 {publiccn.xm.exam.test.Test1();

Code:0: aload_01: invokespecial #1 //Method java/lang/Object."":()V

4: return

public static voidmain(java.lang.String[]);

Code:0: new #2 //class java/util/ArrayList

3: dup4: invokespecial #3 //Method java/util/ArrayList."":()V

7: astore_18: aload_19: ldc #4 //String 111

11: invokeinterface #5, 2 //InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z

16: pop17: new #6 //class java/lang/Thread

20: dup21: new #7 //class cn/xm/exam/test/Test1$1

24: dup25: aload_126: invokespecial #8 //Method cn/xm/exam/test/Test1$1."":(Ljava/util/List;)V

29: invokespecial #9 //Method java/lang/Thread."":(Ljava/lang/Runnable;)V

32: invokevirtual #10 //Method java/lang/Thread.start:()V

35: ldc #11 //String xxx

37: invokestatic #12 //Method test1:(Ljava/lang/Object;)V

40: return

public static voidtest1(java.lang.Object);

Code:0: new #6 //class java/lang/Thread

3: dup4: new #13 //class cn/xm/exam/test/Test1$2

7: dup8: aload_09: invokespecial #14 //Method cn/xm/exam/test/Test1$2."":(Ljava/lang/Object;)V

12: invokespecial #9 //Method java/lang/Thread."":(Ljava/lang/Runnable;)V

15: invokevirtual #10 //Method java/lang/Thread.start:()V

18: return}

2.通路靜态成員變量

靜态變量非常容易了解,直接通過 類名.屬性在任何地方都可以通路到,是以不用final修飾也可以。

packagecn.xm.exam.test;importjava.util.ArrayList;importjava.util.List;public classTest3 {private static List list = new ArrayList<>();static{

list.add("111");

}public static voidmain(String[] args) {new Thread(newRunnable() {

@Overridepublic voidrun() {

System.out.println(list);

}

}).start();

}

}

結果:

[111]

反編譯與反彙編分别檢視源碼:編譯之後也是兩個class檔案:

C:\Users\liqiang\Desktop\建立檔案夾>javac Test3.java

注: Test3.java使用了未經檢查或不安全的操作。

注: 有關詳細資訊, 請使用-Xlint:unchecked 重新編譯。

C:\Users\liqiang\Desktop\建立檔案夾>ls'Test3$1.class' Test3.class Test3.java

反編譯與反彙編檢視Test3$1:(直接在run方法中通路靜态成員變量)

java使用匿名内部類通路變量_匿名内部類可以通路的變量---靜态成員變量和final修飾的局部變量...

C:\Users\liqiang\Desktop\建立檔案夾>javap -c Test3$1.classCompiled from"Test3.java"

final class cn.xm.exam.test.Test3$1 implementsjava.lang.Runnable {

cn.xm.exam.test.Test3$1();

Code:0: aload_01: invokespecial #1 //Method java/lang/Object."":()V

4: return

public voidrun();

Code:0: getstatic #2 //Field java/lang/System.out:Ljava/io/PrintStream;

3: invokestatic #3 //Method cn/xm/exam/test/Test3.access$000:()Ljava/util/List;

6: invokevirtual #4 //Method java/io/PrintStream.println:(Ljava/lang/Object;)V

9: return}

反彙編檢視Test3.class:(可以看出靜态代碼塊初始化了 list,并且在main函數建立了Test3$1執行個體,調用start方法啟動線程)

C:\Users\liqiang\Desktop\建立檔案夾>javap -c Test3.classCompiled from"Test3.java"

public classcn.xm.exam.test.Test3 {publiccn.xm.exam.test.Test3();

Code:0: aload_01: invokespecial #2 //Method java/lang/Object."":()V

4: return

public static voidmain(java.lang.String[]);

Code:0: new #3 //class java/lang/Thread

3: dup4: new #4 //class cn/xm/exam/test/Test3$1

7: dup8: invokespecial #5 //Method cn/xm/exam/test/Test3$1."":()V

11: invokespecial #6 //Method java/lang/Thread."":(Ljava/lang/Runnable;)V

14: invokevirtual #7 //Method java/lang/Thread.start:()V

17: return

static java.util.List access$000();

Code:0: getstatic #1 //Field list:Ljava/util/List;

3: areturnstatic{};

Code:0: new #8 //class java/util/ArrayList

3: dup4: invokespecial #9 //Method java/util/ArrayList."":()V

7: putstatic #1 //Field list:Ljava/util/List;

10: getstatic #1 //Field list:Ljava/util/List;

13: ldc #10 //String 111

15: invokeinterface #11, 2 //InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z

20: pop21: return}

總結:關于javap指令的詳細用法:

C:\Users\liqiang\Desktop\建立檔案夾>javap

用法: javap其中, 可能的選項包括:-help --help -?輸出此用法消息-version 版本資訊-v -verbose 輸出附加資訊-l 輸出行号和本地變量表-public僅顯示公共類和成員-protected 顯示受保護的/公共類和成員-package 顯示程式包/受保護的/公共類

和成員 (預設)-p -private顯示所有類和成員-c 對代碼進行反彙編-s 輸出内部類型簽名-sysinfo 顯示正在處理的類的

系統資訊 (路徑, 大小, 日期, MD5 散列)-constants 顯示靜态最終常量-classpath 指定查找使用者類檔案的位置-bootclasspath 覆寫引導類檔案的位置