local inner 类 是否在定义它们的范围内维护所有局部变量的副本?
Do local inner classes maintain a copy of all local variables in the scope that they are defined in?
做局部内部classes(我相信包括匿名classes)维护在这些局部内部classes的方法范围内定义的所有变量的副本定义在?
这是因为如果局部内部 classes 正在使用局部变量并且方法超出范围而内部 class 对象仍然存在,则该对象将使用某些东西不再存在。
但是这个说法是真的吗?官方文档中有提到它吗?
这是 this answer 的后续问题。我在那里发表评论但没有得到回复,我认为这也是一个不同的问题,所以在这里提问。
不,局部内部 类 不 "keep a copy",但它们可以引用 final
或者,因为 java-8, effectively final 方法中的局部变量。
更阴险的是,他们保留对 实例 的引用,这可能会导致内存泄漏。
Do local inner classes maintain a copy of all local variables in the scope that they are defined in?
"Copy"是一个有歧义的词。
但是必须进行某种复制才能处理两个作用域,这两个作用域在实例化后各走各的路。
首先,局部内部 classes 只能引用封闭范围的变量,如果那些是 "final or effectively final",正如 javac
所说的那样。
尝试使用其值在局部内部 class 声明后发生变化的变量将产生如下错误消息:
Test.java:13: error: local variables referenced from an inner class must be final or effectively final
System.out.println(i);
^
因为我们只有最终变量,所以可以毫不费力地复制原始值。
至于对象,因为它们 are actually pointers in Java,我们只复制指针而不是整个对象,就像在任何其他对象变量赋值中一样。
这样做会为对象创建一个额外的 "reference",这将阻止垃圾收集器在原始变量超出范围后清理它,只要我们的内部 class 仍然是 运行 .
Is it mentioned somewhere in official docs?
我不这么认为。我唯一能找到的是 §8.1.3 of the Java Language Specification,这是非常模糊的:
When an inner class (whose declaration does not occur in a static context) refers to an instance variable that is a member of a lexically enclosing class, the variable of the corresponding lexically enclosing instance is used.
(而且我不确定 static
方法中的声明是否符合 "static context" 的定义。)
但是,您可以通过使用 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
、一个 String
、一个 Object
和一个 InputStream
,从内部作用域引用。
调用 javac Main.java
会创建两个文件:Test.class
和 Test.class
。
Test.class
并不太有趣,但是 Test.class
揭示了这些变量是如何存储和稍后访问的:
bash$ javap -c -private 'Test.class'
Compiled from "Test.java"
final class Test 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(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
}
所以基本上 javac
只是在内部 class 为外部作用域的每个引用变量创建一个 final
字段和相应的构造函数参数。
我不知道其他编译器是怎么做到的,但是
- 我还没有找到这方面的规范。
- 您可以使用您最喜欢的 Java 反汇编程序检查它们每个人是如何做到的。
为了清楚起见
内部(非静态)class 对象可以访问外部 classes this。在对象上有一些隐式字段,this
、Outer.this、OuterOuter.this 就嵌套而言。
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();
优点:内部classes允许创建一个容器class提供项目,每个项目都可以使用外部this访问容器场.
缺点:如果将这样的内部class对象序列化到文件,它也会序列化外部对象,所以行为可能很奇怪。
(答案应该是显而易见的。)
这里发生了几件事:
- JLS(Java 语言标准)关注语义。从语义上讲,没有复制正在进行(或者更确切地说,复制的效果是无法区分的)。所以它什么也没说。
- 编译器关注实现。创建变量的 under-the-hood 副本是实现 local-variable 捕获语义的一种非常简单的方法(给定 non-mutation)。
- JLS 的作者当然知道以上几点,因此几乎可以肯定地策划了相应的语义(特别是 final/effectively-final 周围的约束)。
所以重新审视你的标题问题:
Do local inner classes maintain a copy of all local variables in the scope that they are defined in?
答案是 JLS 对此什么也没说,在实际的编译器实现中,它可能只是 final/effectively-final 变量的副本。
做局部内部classes(我相信包括匿名classes)维护在这些局部内部classes的方法范围内定义的所有变量的副本定义在?
这是因为如果局部内部 classes 正在使用局部变量并且方法超出范围而内部 class 对象仍然存在,则该对象将使用某些东西不再存在。
但是这个说法是真的吗?官方文档中有提到它吗?
这是 this answer 的后续问题。我在那里发表评论但没有得到回复,我认为这也是一个不同的问题,所以在这里提问。
不,局部内部 类 不 "keep a copy",但它们可以引用 final
或者,因为 java-8, effectively final 方法中的局部变量。
更阴险的是,他们保留对 实例 的引用,这可能会导致内存泄漏。
Do local inner classes maintain a copy of all local variables in the scope that they are defined in?
"Copy"是一个有歧义的词。
但是必须进行某种复制才能处理两个作用域,这两个作用域在实例化后各走各的路。
首先,局部内部 classes 只能引用封闭范围的变量,如果那些是 "final or effectively final",正如 javac
所说的那样。
尝试使用其值在局部内部 class 声明后发生变化的变量将产生如下错误消息:
Test.java:13: error: local variables referenced from an inner class must be final or effectively final
System.out.println(i);
^
因为我们只有最终变量,所以可以毫不费力地复制原始值。
至于对象,因为它们 are actually pointers in Java,我们只复制指针而不是整个对象,就像在任何其他对象变量赋值中一样。
这样做会为对象创建一个额外的 "reference",这将阻止垃圾收集器在原始变量超出范围后清理它,只要我们的内部 class 仍然是 运行 .
Is it mentioned somewhere in official docs?
我不这么认为。我唯一能找到的是 §8.1.3 of the Java Language Specification,这是非常模糊的:
When an inner class (whose declaration does not occur in a static context) refers to an instance variable that is a member of a lexically enclosing class, the variable of the corresponding lexically enclosing instance is used.
(而且我不确定 static
方法中的声明是否符合 "static context" 的定义。)
但是,您可以通过使用 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
、一个 String
、一个 Object
和一个 InputStream
,从内部作用域引用。
调用 javac Main.java
会创建两个文件:Test.class
和 Test.class
。
Test.class
并不太有趣,但是 Test.class
揭示了这些变量是如何存储和稍后访问的:
bash$ javap -c -private 'Test.class'
Compiled from "Test.java"
final class Test 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(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
}
所以基本上 javac
只是在内部 class 为外部作用域的每个引用变量创建一个 final
字段和相应的构造函数参数。
我不知道其他编译器是怎么做到的,但是
- 我还没有找到这方面的规范。
- 您可以使用您最喜欢的 Java 反汇编程序检查它们每个人是如何做到的。
为了清楚起见
内部(非静态)class 对象可以访问外部 classes this。在对象上有一些隐式字段,this
、Outer.this、OuterOuter.this 就嵌套而言。
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();
优点:内部classes允许创建一个容器class提供项目,每个项目都可以使用外部this访问容器场.
缺点:如果将这样的内部class对象序列化到文件,它也会序列化外部对象,所以行为可能很奇怪。
(答案应该是显而易见的。)
这里发生了几件事:
- JLS(Java 语言标准)关注语义。从语义上讲,没有复制正在进行(或者更确切地说,复制的效果是无法区分的)。所以它什么也没说。
- 编译器关注实现。创建变量的 under-the-hood 副本是实现 local-variable 捕获语义的一种非常简单的方法(给定 non-mutation)。
- JLS 的作者当然知道以上几点,因此几乎可以肯定地策划了相应的语义(特别是 final/effectively-final 周围的约束)。
所以重新审视你的标题问题:
Do local inner classes maintain a copy of all local variables in the scope that they are defined in?
答案是 JLS 对此什么也没说,在实际的编译器实现中,它可能只是 final/effectively-final 变量的副本。