zoukankan      html  css  js  c++  java
  • 匿名内部类可以访问的变量---静态成员变量和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:UsersliqiangDesktop新建文件夹>ls
    'Test1$1.class'  'Test1$2.class'   Test1.class   Test1.java

    (1)查看Test1$1.class(可以理解为Test1的第一个内部类,实际是将内部访问的final修饰的变量作为参数传入此类的构造方法):

    javap反汇编查看:

    C:UsersliqiangDesktop新建文件夹>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:UsersliqiangDesktop新建文件夹>javac Test3.java
    注: Test3.java使用了未经检查或不安全的操作。
    注: 有关详细信息, 请使用 -Xlint:unchecked 重新编译。
    
    C:UsersliqiangDesktop新建文件夹>ls
    'Test3$1.class'   Test3.class   Test3.java

     反编译与反汇编查看Test3$1:(直接在run方法中访问静态成员变量)

    C:UsersliqiangDesktop新建文件夹>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:UsersliqiangDesktop新建文件夹>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:UsersliqiangDesktop新建文件夹>javap
    用法: javap <options> <classes>
    其中, 可能的选项包括:
      -help  --help  -?        输出此用法消息
      -version                 版本信息
      -v  -verbose             输出附加信息
      -l                       输出行号和本地变量表
      -public                  仅显示公共类和成员
      -protected               显示受保护的/公共类和成员
      -package                 显示程序包/受保护的/公共类
                               和成员 (默认)
      -p  -private             显示所有类和成员
      -c                       对代码进行反汇编
      -s                       输出内部类型签名
      -sysinfo                 显示正在处理的类的
                               系统信息 (路径, 大小, 日期, MD5 散列)
      -constants               显示静态最终常量
      -classpath <path>        指定查找用户类文件的位置
      -bootclasspath <path>    覆盖引导类文件的位置
  • 相关阅读:
    闭包问题小总结
    git常用命令总结
    数组对象方法的使用
    JS截取URL参数
    CSS清除浮动&内容居中&文字溢出
    网址大全
    any more
    下载网页视频
    下载无损音乐
    "前端" 宝藏网站
  • 原文地址:https://www.cnblogs.com/qlqwjy/p/10099763.html
Copyright © 2011-2022 走看看