java使用匿名内部类访问变量_匿名内部类可以访问的变量---静态成员变量和final修饰的局部变量...

本文介绍了Java匿名内部类如何访问静态成员变量和final修饰的局部变量。当匿名内部类访问局部变量时,该变量必须是final的,以确保在内部类的生命周期中保持一致性。编译后,final变量会作为构造方法参数传递给内部类。对于静态成员变量,匿名内部类可以直接访问,无需final修饰。文章通过示例代码和反编译结果进行了详细解释。
摘要由CSDN通过智能技术生成

在学习多线程的时候用到了匿名内部类,匿名内部类可以访问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修饰的变量作为参数传入此类的构造方法):

40ab9f3f432d23a02a10dcfe2a13215c.png

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修饰的变量作为参数传入此类的构造方法):

982c8c7fbc227875d158880788de20bc.png

(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方法中访问静态成员变量)

58147f76b6851c3812a440317d2e6436.png

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 覆盖引导类文件的位置

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值