Java 局部内部类是否在定义它们的范围内维护所有局部变量的副本?
局部内部类(我相信包括匿名类)是否维护在这些局部内部类所定义的方法范围内定义的所有变量的副本 这是因为,如果局部内部类使用局部变量,而该方法在内部类对象仍处于活动状态时超出范围,则该对象将使用不再存在的对象 但这句话是真的吗?在官方文件中有提到吗Java 局部内部类是否在定义它们的范围内维护所有局部变量的副本?,java,inner-classes,final,local-variables,anonymous-inner-class,java-8,Java,Inner Classes,Final,Local Variables,Anonymous Inner Class,Java 8,局部内部类(我相信包括匿名类)是否维护在这些局部内部类所定义的方法范围内定义的所有变量的副本 这是因为,如果局部内部类使用局部变量,而该方法在内部类对象仍处于活动状态时超出范围,则该对象将使用不再存在的对象 但这句话是真的吗?在官方文件中有提到吗 这是我们的后续问题。我在那里留下了一条评论,但没有得到回应,我想这也是一个不同的问题,所以在这里提问。不,局部内部类不“保留副本”,但它们可以引用final或者,因为,方法中的局部变量 更隐秘的是,它们保留对实例的引用,这可能会导致内存泄漏。不,局部内
这是我们的后续问题。我在那里留下了一条评论,但没有得到回应,我想这也是一个不同的问题,所以在这里提问。不,局部内部类不“保留副本”,但它们可以引用
final
或者,因为,方法中的局部变量
更隐秘的是,它们保留对实例的引用,这可能会导致内存泄漏。不,局部内部类不“保留副本”,但它们可以引用
final
或者,因为,方法中的局部变量
更隐秘的是,它们保留对实例的引用,这可能导致内存泄漏 局部内部类是否在定义它们的范围内维护所有局部变量的副本 “复制”是一个模棱两可的术语。
但必须进行某种复制,以处理两个作用域,这两个作用域在实例化后各有不同 首先,局部内部类只能引用封闭作用域的变量,如果这些变量是“final或effectivefinal”,正如
javac
所称
尝试使用其值在本地内部类声明后发生更改的变量将产生如下错误消息:
Test.java:13:error:从内部类引用的局部变量必须是final或有效final
系统输出打印LN(i);
^
因为我们只有最终的变量,所以原语值可以被复制而不会有任何麻烦
至于对象,因为它们是指针,所以我们只复制指针,而不复制整个对象,就像在任何其他对象变量赋值中一样。这样做会创建一个对对象的附加“引用”,这将防止垃圾收集器在原始变量超出作用域时清理它,只要我们的内部类仍在运行 在官方文件中有提到吗 我不这么认为。我唯一能找到的东西是在里面,这很模糊: 当内部类(其声明不会出现在静态上下文中)引用作为词汇封闭类成员的实例变量时,将使用相应词汇封闭实例的变量 (我不确定
static
方法中的声明是否符合“静态上下文”的定义。)
但是,您可以通过使用javap-c-private…
反汇编.class
文件来检查javac
如何处理这个问题
(从现在开始假设Java 8)给定以下代码:
class Test
{
public static void main(String[] args)
{
int i = 42;
String s = "abc";
Object o = new Object();
java.io.InputStream in = null;
Runnable r = new Runnable()
{
public void run()
{
System.out.println(i);
System.out.println(s);
System.out.println(o);
System.out.println(in);
}
};
}
}
因此,我们在外部范围中有一个int
、一个字符串
、一个对象
和一个输入流
,从内部范围引用
调用javac Main.java
将创建两个文件:Test.class
和Test$1.class
Test.class
不太有趣,但Test$1.class
揭示了这些变量在以后的存储和访问方式:
bash$ javap -c -private 'Test$1.class'
Compiled from "Test.java"
final class Test$1 implements java.lang.Runnable {
final int val$i;
final java.lang.String val$s;
final java.lang.Object val$o;
final java.io.InputStream val$in;
Test$1(int, java.lang.String, java.lang.Object, java.io.InputStream);
Code:
0: aload_0
1: iload_1
2: putfield #1 // Field val$i:I
5: aload_0
6: aload_2
7: putfield #2 // Field val$s:Ljava/lang/String;
10: aload_0
11: aload_3
12: putfield #3 // Field val$o:Ljava/lang/Object;
15: aload_0
16: aload 4
18: putfield #4 // Field val$in:Ljava/io/InputStream;
21: aload_0
22: invokespecial #5 // Method java/lang/Object."<init>":()V
25: return
public void run();
Code:
0: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
3: aload_0
4: getfield #1 // Field val$i:I
7: invokevirtual #7 // Method java/io/PrintStream.println:(I)V
10: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
13: aload_0
14: getfield #2 // Field val$s:Ljava/lang/String;
17: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
20: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
23: aload_0
24: getfield #3 // Field val$o:Ljava/lang/Object;
27: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
30: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
33: aload_0
34: getfield #4 // Field val$in:Ljava/io/InputStream;
37: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
40: return
}
bash$javap-c-private'Test$1.class'
从“Test.java”编译而来
最后一个类测试$1实现java.lang.Runnable{
最终int val$i;
最终java.lang.String val$s;
最终java.lang.Object val$o;
最终java.io.InputStream val$in;
测试$1(int、java.lang.String、java.lang.Object、java.io.InputStream);
代码:
0:aload_0
1:iload_1
2:putfield#1//字段值$i:i
5:aload_0
6:aload_2
7:putfield#2//Field val$s:Ljava/lang/String;
10:aload_0
11:aload_3
12:putfield#3//Field val$o:Ljava/lang/Object;
15:aload_0
16:aload 4
18:putfield#4//Field val$in:Ljava/io/InputStream;
21:aload_0
22:invokespecial#5//方法java/lang/Object。”“:()V
25:返回
公开无效运行();
代码:
0:getstatic#6//Field java/lang/System.out:Ljava/io/PrintStream;
3:aload_0
4:getfield#1//Field val$i:i
7:invokevirtual#7//方法java/io/PrintStream.println:(I)V
10:getstatic#6//fieldjava/lang/System.out:Ljava/io/PrintStream;
13:aload_0
14:getfield#2//Field val$s:Ljava/lang/String;
17:invokevirtual#8//方法java/io/PrintStream.println:(Ljava/lang/String;)V
20:getstatic#6//fieldjava/lang/System.out:Ljava/io/PrintStream;
23:aload_0
24:getfield#3//Field val$o:Ljava/lang/Object;
27:invokevirtual#9//方法java/io/PrintStream.println:(Ljava/lang/Object;)V
30:getstatic#6//fieldjava/lang/System.out:Ljava/io/PrintStream;
33:aload_0
34:getfield#4//字段val$in:Ljava/io/InputStream;
37:invokevirtual#9//方法java/io/PrintStream.println:(Ljava/lang/Object;)V
40:返回
}
因此,基本上javac
只需在内部类中为外部范围的每个引用变量创建一个final
字段和一个相应的构造函数参数
我不知道其他编译器是如何做到这一点的,但是
- 我还没有找到t的规格
class Outer { int outerField; class Inner { String innerField; ... innerField ... outerField ... this.innerField ... Outer.this } ... new Inner(); } Outer outer = new Outer(); Inner inner = outer.new Inner();