在學習多線程的時候用到了匿名內部類,匿名內部類可以訪問static靜態成員變量或者final修飾的局部變量。
匿名內部類在編譯之后會生成class文件,比如Test內的第一個匿名內部類編譯之后就是Test$1.class;
匿名內部類中訪問的final修飾的局部變量在生成Test$1.class之后會作為構造方法的參數傳入class中;如果匿名內部類訪問的是另一個類的靜態成員變量則直接訪問,不會作為構造方法的參數。
1.訪問final修飾的局部變量
局部變量需要是final修飾,如果訪問方法參數,方法的參數也需要是final修飾的
package cn.xm.exam.test; import java.util.ArrayList; import java.util.List; public class Test1 { public static void main(String[] args) { final List list = new ArrayList<>(); list.add("111"); new Thread(new Runnable() { @Override public void run() { System.out.println(list + ",threadName->" + Thread.currentThread().getName()); } }).start(); test1("xxx"); } public static void test1(final Object object) { new Thread(new Runnable() { @Override public void run() { 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修飾的變量作為參數傳入此類的構造方法):
javap反匯編查看:
C:\Users\liqiang\Desktop\新建文件夾>javap -c Test1$1.class Compiled from "Test1.java" final class cn.xm.exam.test.Test1$1 implements java.lang.Runnable { final java.util.List val$list; cn.xm.exam.test.Test1$1(java.util.List); Code: 0: aload_0 1: aload_1 2: putfield #1 // Field val$list:Ljava/util/List; 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: new #4 // class java/lang/StringBuilder 6: dup 7: invokespecial #5 // Method java/lang/StringBuilder."<init>":()V 10: aload_0 11: 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修飾的變量作為參數傳入此類的構造方法):
(3)查看Test1.class
反編譯看不出來,直接反匯編查看:可以看出是創建了對應的匿名內部類,並且將參數摻入構造方法中(main方法創建Test1$1類實例,test1方法創建Test2$2類實例)
Compiled from "Test1.java" public class cn.xm.exam.test.Test1 { public cn.xm.exam.test.Test1(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: new #2 // class java/util/ArrayList 3: dup 4: invokespecial #3 // Method java/util/ArrayList."<init>":()V 7: astore_1 8: aload_1 9: ldc #4 // String 111 11: invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z 16: pop 17: new #6 // class java/lang/Thread 20: dup 21: new #7 // class cn/xm/exam/test/Test1$1 24: dup 25: aload_1 26: invokespecial #8 // Method cn/xm/exam/test/Test1$1."<init>":(Ljava/util/List;)V 29: invokespecial #9 // Method java/lang/Thread."<init>":(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 void test1(java.lang.Object); Code: 0: new #6 // class java/lang/Thread 3: dup 4: new #13 // class cn/xm/exam/test/Test1$2 7: dup 8: aload_0 9: invokespecial #14 // Method cn/xm/exam/test/Test1$2."<init>":(Ljava/lang/Object;)V 12: invokespecial #9 // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V 15: invokevirtual #10 // Method java/lang/Thread.start:()V 18: return }
2.訪問靜態成員變量
靜態變量非常容易理解,直接通過 類名.屬性在任何地方都可以訪問到,所以不用final修飾也可以。
package cn.xm.exam.test; import java.util.ArrayList; import java.util.List; public class Test3 { private static List list = new ArrayList<>(); static { list.add("111"); } public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { 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方法中訪問靜態成員變量)
C:\Users\liqiang\Desktop\新建文件夾>javap -c Test3$1.class Compiled from "Test3.java" final class cn.xm.exam.test.Test3$1 implements java.lang.Runnable { cn.xm.exam.test.Test3$1(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public void run(); 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.class Compiled from "Test3.java" public class cn.xm.exam.test.Test3 { public cn.xm.exam.test.Test3(); Code: 0: aload_0 1: invokespecial #2 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: new #3 // class java/lang/Thread 3: dup 4: new #4 // class cn/xm/exam/test/Test3$1 7: dup 8: invokespecial #5 // Method cn/xm/exam/test/Test3$1."<init>":()V 11: invokespecial #6 // Method java/lang/Thread."<init>":(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: areturn static {}; Code: 0: new #8 // class java/util/ArrayList 3: dup 4: invokespecial #9 // Method java/util/ArrayList."<init>":()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: pop 21: return }
總結:關於javap命令的詳細用法:
C:\Users\liqiang\Desktop\新建文件夾>javap 用法: javap <options> <classes> 其中, 可能的選項包括: -help --help -? 輸出此用法消息 -version 版本信息 -v -verbose 輸出附加信息 -l 輸出行號和本地變量表 -public 僅顯示公共類和成員 -protected 顯示受保護的/公共類和成員 -package 顯示程序包/受保護的/公共類 和成員 (默認) -p -private 顯示所有類和成員 -c 對代碼進行反匯編 -s 輸出內部類型簽名 -sysinfo 顯示正在處理的類的 系統信息 (路徑, 大小, 日期, MD5 散列) -constants 顯示靜態最終常量 -classpath <path> 指定查找用戶類文件的位置 -bootclasspath <path> 覆蓋引導類文件的位置