在学习多线程的时候用到了匿名内部类,匿名内部类可以访问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> 覆盖引导类文件的位置

匿名内部类可以访问的变量---静态成员变量和final修饰的局部变量的更多相关文章

  1. 为什么匿名内部类只能访问其所在方法中的final类型的局部变量?

    大部分时候,类被定义成一个独立的程序单元.在某些情况下,也会把一个类放在另一个类的内部定义,这个定义在其他类内部的类就被称为内部类,包含内部类的类也被称为外部类. class Outer { priv ...

  2. 从垃圾回收机制解析为什么局部内部类只能访问final修饰的局部变量以及为什么加final能解决问题

    我们先稍微看一下代码: 从这里的提示可以看到,必须要将a的修饰符变为final才行. 现在笔者就这一结果做出自己的分析: 首先来说,我们知道,方法被调用时会执行,当执行的时候,方法中的局部变量会加载到 ...

  3. 静态变量和成员变量的区别、final修饰特点、创建对象的内存图、静态内存图

    静态变量和成员变量的区别* 静态变量也叫类变量  成员变量也叫对象变量* A:所属不同 * 静态变量属于类,所以也称为为类变量 * 成员变量属于对象,所以也称为实例变量(对象变量)* B:内存中位置不 ...

  4. Java学习笔记8---类的静态成员变量与静态成员方法的访问与调用方式

    (注:静态变量修改为静态成员变量,静态方法改为静态成员方法) 静态成员变量又称类变量,静态成员方法又称类方法,它们统称为静态成员或类成员.静态成员由static修饰,是属于整个类的,所有的对象共享这些 ...

  5. 13.C++-静态成员变量、静态成员函数

    首先回顾下成员变量 能通过对象名能够访问public成员变量 每个对象的成员变量都是专属的 成员变量不能在对象之间共享 再来讲讲类的静态成员变量 介绍 静态成员变量属于整个类所有 静态成员变量的生命期 ...

  6. C++解析(14):静态成员变量与静态成员函数

    0.目录 1.静态成员变量 2.静态成员函数 3.小结 1.静态成员变量 成员变量的回顾: 通过对象名能够访问public成员变量 每个对象的成员变量都是专属的 成员变量不能在对象之间共享 新的需求: ...

  7. Java面向对象-static关键字、静态方法与普通方法、静态成员变量

    Java面向对象-static关键字.静态方法与普通方法 static关键字的基本作用:方便在没有创建对象的情况下来进行调用(方法/变量). 很显然,被static关键字修饰的方法或者变量不需要依赖于 ...

  8. JAVA非静态成员变量之死循环

    1.非静态成员变量 当成员变量为非静态成员变量且对当前类进行实例化时,将会产生死循环 例子: public class ConstructorCls { private ConstructorCls ...

  9. 【转】C#父类与子类的静态成员变量、实例成员变量、构造函数的执行顺序

    原文地址:http://www.xuebuyuan.com/1092603.html Win7+VS2010测试的结果如下: ①子类静态成员变量②子类静态构造函数③子类实例成员变量④父类静态成员变量⑤ ...

随机推荐

  1. 深入理解JS函数中this指针的指向

    函数在执行时,会在函数体内部自动生成一个this指针.谁直接调用产生这个this指针的函数,this就指向谁. 怎么理解指向呢,我认为指向就是等于.例如直接在js中输入下面的等式: console.l ...

  2. Linux系统下权限管理和命令详解

    下面对linux系统下的有关权限操作命令进行了梳理总结,并配合简单实例进行说明.linux中除了常见的读(r).写(w).执行(x)权限以外,还有其他的一些特殊或隐藏权限,熟练掌握这些权限知识的使用, ...

  3. Nginx安装及配置详解包括windows环境

    nginx概述 nginx是一款自由的.开源的.高性能的HTTP服务器和反向代理服务器:同时也是一个IMAP.POP3.SMTP代理服务器:nginx可以作为一个HTTP服务器进行网站的发布处理,另外 ...

  4. 开源实时消息推送系统 MPush

    系统介绍 mpush,是一款开源的实时消息推送系统,采用java语言开发,服务端采用模块化设计,具有协议简洁,传输安全,接口流畅,实时高效,扩展性强,可配置化,部署方便,监控完善等特点.同时也是少有的 ...

  5. Sublime Text3 里使用MarkDown如何预览

    安装需要的包: 1.markdown editing 2.markdown preview 具体的步骤是: 1.按住ctrl + shift + p 来调出一个弹出的输入框 :2.输入package  ...

  6. https笔记【转】

    图解HTTPS 我们都知道HTTPS能够加密信息,以免敏感信息被第三方获取.所以很多银行网站或电子邮箱等等安全级别较高的服务都会采用HTTPS协议. HTTPS简介 HTTPS其实是有两部分组成:HT ...

  7. VScode 1.13 gocode提示dial tcp 216.239.37.1:443: connectex: A connection attempt failed because the connected..

    在将VScode升级至 1.13后让升级gocode,在升级时报出如下错误 D:\go_work\src>go get -u -v github.com/mdempsky/gocode gith ...

  8. 09、 在QQ音乐中查找七里香这首歌的精彩评论

       找到七里香这首歌的精彩评论      URL https://c.y.qq.com/base/fcgi-bin/fcg_global_comment_h5.fcg?g_tk=5381&l ...

  9. AndroidStudio替换空行

    (1)在Edit Replace In Path输入框中输入:^\s*\n (\s代表任何空白字符,\S代表任何非空白字符,*代表任意个数,\n匹配换行符) (2)Replace With输入框的值为 ...

  10. Retrofit GreenDao开发中遇到的坑

    持续更新中1.使用@FormUrlEncoded的话,服务端需要使用Request.Form,如果不使用@FormUrlEncoded本地需要由 @FieldMap Map<String, Ob ...