这个问题是关于Java的有趣的现象:它产生额外的(不是默认的)构造函数在某些情况下,嵌套类。
这个问题也是关于奇怪的匿名类,它的Java与奇怪的构造产生。
考虑下面的代码:
package a;
import java.lang.reflect.Constructor;
public class TestNested {
class A {
A() {
}
A(int a) {
}
}
public static void main(String[] args) {
Class<A> aClass = A.class;
for (Constructor c : aClass.getDeclaredConstructors()) {
System.out.println(c);
}
}
}
这将打印:
a.TestNested$A(a.TestNested)
a.TestNested$A(a.TestNested,int)
好。 接下来,让我们使构造A(int a)
私人:
private A(int a) {
}
再次运行程序。 接收:
a.TestNested$A(a.TestNested)
private a.TestNested$A(a.TestNested,int)
这也OK。 但现在,让我们修改main()
以这样的方式(除了新的类实例的方法, A
创造):
public static void main(String[] args) {
Class<A> aClass = A.class;
for (Constructor c : aClass.getDeclaredConstructors()) {
System.out.println(c);
}
A a = new TestNested().new A(123); // new line of code
}
然后输入变为:
a.TestNested$A(a.TestNested)
private a.TestNested$A(a.TestNested,int)
a.TestNested$A(a.TestNested,int,a.TestNested$1)
这是什么:a.TestNested $ A(a.TestNested,INT,a.TestNested $ 1)<<< ---?
好了,再让使构造A(int a)
包本地:
A(int a) {
}
再次运行程序(我们不的实例中删除线A
!创造),输出是在第一时间:
a.TestNested$A(a.TestNested)
a.TestNested$A(a.TestNested,int)
问题:
1)这到底是怎么解释呢?
2)这是什么第三怪构造?
更新:下面显示的调查。
1)让我们尝试调用使用反射来自其他类这个奇怪的构造函数。 我们将不能够做到这一点,因为没有任何方法来创建一个奇怪的实例TestNested$1
类。
2)确定。 让我们做的伎俩。 让我们来添加到类TestNested
这样的静态字段:
public static Object object = new Object() {
public void print() {
System.out.println("sss");
}
};
好? 好了,现在我们可以从另一个调用类第三奇怪的构造函数:
TestNested tn = new TestNested();
TestNested.A a = (TestNested.A)TestNested.A.class.getDeclaredConstructors()[2].newInstance(tn, 123, TestNested.object);
很抱歉,但我绝对不明白。
更新2:进一步的问题是:
3)为什么Java的使用特殊的匿名内部类的参数类型为第三复合构造? 为什么不只是Object
的特殊名称类型构造的,?
4)什么的Java可以使用已定义的匿名内部类的目的是什么? 这不是某种违反安全的?
首先,感谢您对本有趣的问题。 我很好奇,我忍不住考虑看看字节码。 这是字节码TestNested
:
Compiled from "TestNested.java"
public class a.TestNested {
public a.TestNested();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: ldc_w #2 // class a/TestNested$A
3: astore_1
4: aload_1
5: invokevirtual #3 // Method java/lang/Class.getDeclaredConstructors:()[Ljava/lang/reflect/Constructor;
8: astore_2
9: aload_2
10: arraylength
11: istore_3
12: iconst_0
13: istore 4
15: iload 4
17: iload_3
18: if_icmpge 41
21: aload_2
22: iload 4
24: aaload
25: astore 5
27: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
30: aload 5
32: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
35: iinc 4, 1
38: goto 15
41: new #2 // class a/TestNested$A
44: dup
45: new #6 // class a/TestNested
48: dup
49: invokespecial #7 // Method "<init>":()V
52: dup
53: invokevirtual #8 // Method java/lang/Object.getClass:()Ljava/lang/Class;
56: pop
57: bipush 123
59: aconst_null
60: invokespecial #9 // Method a/TestNested$A."<init>":(La/TestNested;ILa/TestNested$1;)V
63: astore_2
64: return
}
正如你所看到的,构造a.TestNested$A(a.TestNested,int,a.TestNested$1)
从您的调用main
方法。 此外, null
是作为价值传递a.TestNested$1
参数。
因此,让我们来看看神秘的匿名类a.TestNested$1
:
Compiled from "TestNested.java"
class a.TestNested$1 {
}
奇怪 - 我本来期望这个类实际上做一些事情。 要理解它,让我们来看看在车队a.TestNested$A
:类a.TestNested $ A {最终a.TestNested此$ 0;
a.TestNested$A(a.TestNested);
Code:
0: aload_0
1: aload_1
2: putfield #2 // Field this$0:La/TestNested;
5: aload_0
6: invokespecial #3 // Method java/lang/Object."<init>":()V
9: return
private a.TestNested$A(a.TestNested, int);
Code:
0: aload_0
1: aload_1
2: putfield #2 // Field this$0:La/TestNested;
5: aload_0
6: invokespecial #3 // Method java/lang/Object."<init>":()V
9: return
a.TestNested$A(a.TestNested, int, a.TestNested$1);
Code:
0: aload_0
1: aload_1
2: iload_2
3: invokespecial #1 // Method "<init>":(La/TestNested;I)V
6: return
}
纵观包可见构造a.TestNested$A(a.TestNested, int, a.TestNested$1)
我们可以看到,第三个参数被忽略。
现在,我们可以解释的构造函数和匿名内部类。 额外的构造要求,以规避对私有构造能见度限制。 这种额外的构造简单地委托给私有的构造函数。 但是,它不能有相同的签名私有构造函数。 正因为如此,匿名内部类被添加到不与其它可能的重载构造,诸如具有签名构造碰撞提供一个独特的签名(int,int)
或(int,Object)
。 由于这个匿名内部类只需要创建一个独特的签名,它不需要被实例化,也不需要有内容。
第三个构造是由编译器生成,以允许从外部类访问私有构造函数的合成构造函数。 这是因为内部类(和它们的封闭班到他们的私有成员访问)只存在于Java语言,而不是JVM,所以编译器必须弥合幕后的差距。
反思会告诉你,如果一个成员是合成的:
for (Constructor c : aClass.getDeclaredConstructors()) {
System.out.println(c + " " + c.isSynthetic());
}
这将打印:
a.TestNested$A(a.TestNested) false
private a.TestNested$A(a.TestNested,int) false
a.TestNested$A(a.TestNested,int,a.TestNested$1) true
看到这个帖子供进一步讨论: Eclipse的警告有关Java中的私有静态嵌套类合成的访问?
编辑:有趣的是,Eclipse编译器做它不同于javac的。 当使用蚀,它增加了内部类本身的类型的参数:
a.TestNested$A(a.TestNested) false
private a.TestNested$A(a.TestNested,int) false
a.TestNested$A(a.TestNested,int,a.TestNested$A) true
我试图通过提前曝光的时间构造绊倒它:
class A {
A() {
}
private A(int a) {
}
A(int a, A another) { }
}
它处理这个通过简单地添加另一种说法来合成的构造函数:
a.TestNested$A(a.TestNested) false
private a.TestNested$A(a.TestNested,int) false
a.TestNested$A(a.TestNested,int,a.TestNested$A) false
a.TestNested$A(a.TestNested,int,a.TestNested$A,a.TestNested$A) true