匿名內部類可以訪問的變量---靜態成員變量和final修飾的局部變量


  在學習多線程的時候用到了匿名內部類,匿名內部類可以訪問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>    覆蓋引導類文件的位置

 


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com