Java: 匿名类的初始化和构造函数
Java: initialization and costructor of anonymous classes
我想了解我在处理匿名 classes 时遇到的一个奇怪行为。
我有一个 class 在其构造函数中调用受保护的方法(我知道,设计不佳,但那是另一回事...)
public class A {
public A() {
init();
}
protected void init() {}
}
然后我有另一个 class 扩展 A
并覆盖 init()
.
public class B extends A {
int value;
public B(int i) {
value = i;
}
protected void init() {
System.out.println("value="+value);
}
}
如果我编码
B b = new B(10);
我明白了
> value=0
这是预料之中的,因为超级 class 的构造函数在 B
构造函数之前被调用,然后 value
仍然存在。
但是当像这样使用匿名class时
class C {
public static void main (String[] args) {
final int avalue = Integer.parsetInt(args[0]);
A a = new A() {
void init() { System.out.println("value="+avalue); }
}
}
}
我希望得到 value=0
因为这应该或多或少等于 class B
:编译器会自动创建一个新的 class C
扩展 A
并创建实例变量来存储在匿名方法中引用的局部变量 class,模拟闭包等...
但是当你运行这个的时候,我得到了
> java -cp . C 42
> value=42
最初我认为这是因为我使用的是 java 8,也许,在引入 lamdbas 时,他们改变了匿名 classes 在引擎盖下的实现方式(您不再需要 final
),但我也尝试使用 java 7 并得到相同的结果...
其实看javap
的字节码,可以看出B
是
> javap -c B
Compiled from "B.java"
public class B extends A {
int value;
public B(int);
Code:
0: aload_0
1: invokespecial #1 // Method A."<init>":()V
4: aload_0
5: iload_1
6: putfield #2 // Field value:I
9: return
...
而 C
:
> javap -c C$1
Compiled from "C.java"
final class C extends A {
final int val$v;
C(int);
Code:
0: aload_0
1: iload_1
2: putfield #1 // Field val$v:I
5: aload_0
6: invokespecial #2 // Method A."<init>":()V
9: return
....
有人能告诉我为什么会有这种差异吗?
有没有一种方法可以使用 "normal" classes 来复制匿名 class 的行为?
编辑:
澄清问题:为什么匿名 classes 的初始化违反了任何其他 class 的初始化规则(在设置任何其他变量之前调用超级构造函数)?
或者,有没有办法在调用超级构造函数之前在 B
class 中设置实例变量?
您的匿名 class 实例的行为与您的第一个代码片段不同,因为您使用的是局部变量,其值在创建匿名 class 实例之前被初始化。
如果您在匿名 class 中使用实例变量,您可以获得与带有匿名 class 实例的第一个片段类似的行为:
class C {
public static void main (String[] args) {
A a = new A() {
int avalue = 10;
void init() { System.out.println("value="+avalue); }
}
}
}
这将打印
value=0
因为 init()
在 avalue
初始化之前由 A
的构造函数执行。
这两个例子没有关联。
B例中:
protected void init() {
System.out.println("value="+value);
}
正在打印的值是 B 实例的 value
字段。
在匿名示例中:
final int avalue = Integer.parsetInt(args[0]);
A a = new A() {
void init() { System.out.println("value="+avalue); }
}
打印的值是main()
方法的局部变量avalue
。
这个问题适用于所有内部 classes,而不仅仅是匿名 classes。 (匿名 classes 是内部 classes)
JLS 没有规定内部 class 主体如何访问外部局部变量;只有 specifies 局部变量实际上是最终的,并且肯定在内部 class 主体之前分配。所以按理说里面的class肯定是看到了局部变量的明确赋值。
JLS 没有具体说明内部 class 如何看待该值;由编译器使用任何技巧(在字节码级别上可能)来实现该效果。特别是,这个问题与构造函数完全无关(就语言而言)。
一个类似的问题是内部 class 如何访问外部实例。这有点复杂,它确实与构造函数有关 something 。尽管如此,JLS 仍然没有规定编译器如何实现它;该部分包含评论 "...编译器可以随心所欲地表示直接封闭的实例。Java 编程语言不需要..."
从 JMM 的角度来看,这种规格不足可能是个问题;目前还不清楚内部 class 中的写入与读取是如何完成的。可以合理地假设,写操作是在一个合成变量上完成的,它在(按编程顺序)new InnerClass()
操作之前;内部 class 读取合成变量以查看外部局部变量或封闭实例。
Is there a way to replicate the behavior of the anonymous class using "normal" classes?
你可以把"normal"class排列成外-内class
public class B0
{
int value;
public B0(int i){ value=i; }
public class B extends A
{
protected void init()
{
System.out.println("value="+value);
}
}
}
会这样使用,打印出10
new B0(10).new B();
可以添加一个方便的工厂方法来隐藏语法的丑陋
newB(10);
public static B0.B newB(int arg){ return new B0(arg).new B(); }
所以我们将 class 分成两部分;外部甚至在超级构造函数之前执行。这在某些情况下很有用。 (another example)
(内部匿名访问局部变量封闭实例有效的最终超级构造函数)
允许匿名变量捕获类打破普通构造函数的规则(超级构造函数调用必须是第一个语句),因为此法则仅由编译器强制执行。 JVM 允许任何字节码在调用超级构造函数之前是 运行,编译器本身使用它(它打破了它自己的规则!)用于匿名 类.
您可以使用内部 类 模仿行为,如 bayou.io 的回答所示,或者您可以在静态 B
工厂方法中使用匿名:
public class B extends A
{
public static B create(int value)
{
return new B() {
void init() { System.out.println("value="+value);
};
}
}
这个限制实际上毫无意义,在某些情况下可能会很烦人:
class A
{
private int len;
public A(String s)
{
this.len = s.length();
}
}
class B extends A
{
private String complexString;
public B(int i, double d)
{
super(computeComplexString(i, d));
this.complexString = computeComplexString(i, d);
}
private static String computeComplexString(int i, double d)
{
// some code that takes a long time
}
}
在此示例中,您必须执行两次 computeComplexString
计算,因为无法同时将其传递给超级构造函数 和 将其存储在一个实例变量。
我想了解我在处理匿名 classes 时遇到的一个奇怪行为。
我有一个 class 在其构造函数中调用受保护的方法(我知道,设计不佳,但那是另一回事...)
public class A {
public A() {
init();
}
protected void init() {}
}
然后我有另一个 class 扩展 A
并覆盖 init()
.
public class B extends A {
int value;
public B(int i) {
value = i;
}
protected void init() {
System.out.println("value="+value);
}
}
如果我编码
B b = new B(10);
我明白了
> value=0
这是预料之中的,因为超级 class 的构造函数在 B
构造函数之前被调用,然后 value
仍然存在。
但是当像这样使用匿名class时
class C {
public static void main (String[] args) {
final int avalue = Integer.parsetInt(args[0]);
A a = new A() {
void init() { System.out.println("value="+avalue); }
}
}
}
我希望得到 value=0
因为这应该或多或少等于 class B
:编译器会自动创建一个新的 class C
扩展 A
并创建实例变量来存储在匿名方法中引用的局部变量 class,模拟闭包等...
但是当你运行这个的时候,我得到了
> java -cp . C 42
> value=42
最初我认为这是因为我使用的是 java 8,也许,在引入 lamdbas 时,他们改变了匿名 classes 在引擎盖下的实现方式(您不再需要 final
),但我也尝试使用 java 7 并得到相同的结果...
其实看javap
的字节码,可以看出B
是
> javap -c B
Compiled from "B.java"
public class B extends A {
int value;
public B(int);
Code:
0: aload_0
1: invokespecial #1 // Method A."<init>":()V
4: aload_0
5: iload_1
6: putfield #2 // Field value:I
9: return
...
而 C
:
> javap -c C$1
Compiled from "C.java"
final class C extends A {
final int val$v;
C(int);
Code:
0: aload_0
1: iload_1
2: putfield #1 // Field val$v:I
5: aload_0
6: invokespecial #2 // Method A."<init>":()V
9: return
....
有人能告诉我为什么会有这种差异吗? 有没有一种方法可以使用 "normal" classes 来复制匿名 class 的行为?
编辑:
澄清问题:为什么匿名 classes 的初始化违反了任何其他 class 的初始化规则(在设置任何其他变量之前调用超级构造函数)?
或者,有没有办法在调用超级构造函数之前在 B
class 中设置实例变量?
您的匿名 class 实例的行为与您的第一个代码片段不同,因为您使用的是局部变量,其值在创建匿名 class 实例之前被初始化。
如果您在匿名 class 中使用实例变量,您可以获得与带有匿名 class 实例的第一个片段类似的行为:
class C {
public static void main (String[] args) {
A a = new A() {
int avalue = 10;
void init() { System.out.println("value="+avalue); }
}
}
}
这将打印
value=0
因为 init()
在 avalue
初始化之前由 A
的构造函数执行。
这两个例子没有关联。
B例中:
protected void init() {
System.out.println("value="+value);
}
正在打印的值是 B 实例的 value
字段。
在匿名示例中:
final int avalue = Integer.parsetInt(args[0]);
A a = new A() {
void init() { System.out.println("value="+avalue); }
}
打印的值是main()
方法的局部变量avalue
。
这个问题适用于所有内部 classes,而不仅仅是匿名 classes。 (匿名 classes 是内部 classes)
JLS 没有规定内部 class 主体如何访问外部局部变量;只有 specifies 局部变量实际上是最终的,并且肯定在内部 class 主体之前分配。所以按理说里面的class肯定是看到了局部变量的明确赋值。
JLS 没有具体说明内部 class 如何看待该值;由编译器使用任何技巧(在字节码级别上可能)来实现该效果。特别是,这个问题与构造函数完全无关(就语言而言)。
一个类似的问题是内部 class 如何访问外部实例。这有点复杂,它确实与构造函数有关 something 。尽管如此,JLS 仍然没有规定编译器如何实现它;该部分包含评论 "...编译器可以随心所欲地表示直接封闭的实例。Java 编程语言不需要..."
从 JMM 的角度来看,这种规格不足可能是个问题;目前还不清楚内部 class 中的写入与读取是如何完成的。可以合理地假设,写操作是在一个合成变量上完成的,它在(按编程顺序)new InnerClass()
操作之前;内部 class 读取合成变量以查看外部局部变量或封闭实例。
Is there a way to replicate the behavior of the anonymous class using "normal" classes?
你可以把"normal"class排列成外-内class
public class B0
{
int value;
public B0(int i){ value=i; }
public class B extends A
{
protected void init()
{
System.out.println("value="+value);
}
}
}
会这样使用,打印出10
new B0(10).new B();
可以添加一个方便的工厂方法来隐藏语法的丑陋
newB(10);
public static B0.B newB(int arg){ return new B0(arg).new B(); }
所以我们将 class 分成两部分;外部甚至在超级构造函数之前执行。这在某些情况下很有用。 (another example)
(内部匿名访问局部变量封闭实例有效的最终超级构造函数)
允许匿名变量捕获类打破普通构造函数的规则(超级构造函数调用必须是第一个语句),因为此法则仅由编译器强制执行。 JVM 允许任何字节码在调用超级构造函数之前是 运行,编译器本身使用它(它打破了它自己的规则!)用于匿名 类.
您可以使用内部 类 模仿行为,如 bayou.io 的回答所示,或者您可以在静态 B
工厂方法中使用匿名:
public class B extends A
{
public static B create(int value)
{
return new B() {
void init() { System.out.println("value="+value);
};
}
}
这个限制实际上毫无意义,在某些情况下可能会很烦人:
class A
{
private int len;
public A(String s)
{
this.len = s.length();
}
}
class B extends A
{
private String complexString;
public B(int i, double d)
{
super(computeComplexString(i, d));
this.complexString = computeComplexString(i, d);
}
private static String computeComplexString(int i, double d)
{
// some code that takes a long time
}
}
在此示例中,您必须执行两次 computeComplexString
计算,因为无法同时将其传递给超级构造函数 和 将其存储在一个实例变量。