And*_*niy 15 java reflection nested-class
这个问题是关于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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
这将打印:
a.TestNested$A(a.TestNested)
a.TestNested$A(a.TestNested,int)
Run Code Online (Sandbox Code Playgroud)
好.接下来,让构造函数A(int a)私有:
private A(int a) {
}
Run Code Online (Sandbox Code Playgroud)
再次运行程序.接收:
a.TestNested$A(a.TestNested)
private a.TestNested$A(a.TestNested,int)
Run Code Online (Sandbox Code Playgroud)
这也没关系.但是现在,让我们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
}
Run Code Online (Sandbox Code Playgroud)
然后输入变为:
a.TestNested$A(a.TestNested)
private a.TestNested$A(a.TestNested,int)
a.TestNested$A(a.TestNested,int,a.TestNested$1)
Run Code Online (Sandbox Code Playgroud)
它是什么:a.TestNested $ A(a.TestNested,int,a.TestNested $ 1) <<< --- ??
好的,让我们再次使构造函数A(int a)包本地化:
A(int a) {
}
Run Code Online (Sandbox Code Playgroud)
再次重新运行程序(我们不删除A创建实例的行!),输出如第一次:
a.TestNested$A(a.TestNested)
a.TestNested$A(a.TestNested,int)
Run Code Online (Sandbox Code Playgroud)
问题:
1)如何解释这个?
2)这第三个奇怪的构造函数是什么?
更新:调查显示如下.
1)让我们尝试使用来自其他类的反射来调用这个奇怪的构造函数.我们无法做到这一点,因为没有办法创建那个奇怪TestNested$1类的实例.
2)好的.让我们来做.让我们在类中添加TestNested这样的静态字段:
public static Object object = new Object() {
public void print() {
System.out.println("sss");
}
};
Run Code Online (Sandbox Code Playgroud)
好?好的,现在我们可以从另一个类调用第三个奇怪的构造函数:
TestNested tn = new TestNested();
TestNested.A a = (TestNested.A)TestNested.A.class.getDeclaredConstructors()[2].newInstance(tn, 123, TestNested.object);
Run Code Online (Sandbox Code Playgroud)
对不起,我绝对不明白.
更新-2:进一步的问题是:
3)为什么Java使用特殊的匿名内部类作为第三个合成构造函数的参数类型?为什么不直接Object输入具有特殊名称的构造函数?
4) Java可以使用已经为这些目的定义的匿名内部类?这不是某种违反安全的行为吗?
第三个构造函数是由编译器生成的合成构造函数,以允许从外部类访问私有构造函数.这是因为内部类(及其封闭类对其私有成员的访问)仅存在于Java语言而不是JVM,因此编译器必须弥合幕后的差距.
反思会告诉你一个成员是否是合成的:
for (Constructor c : aClass.getDeclaredConstructors()) {
System.out.println(c + " " + c.isSynthetic());
}
Run Code Online (Sandbox Code Playgroud)
这打印:
a.TestNested$A(a.TestNested) false
private a.TestNested$A(a.TestNested,int) false
a.TestNested$A(a.TestNested,int,a.TestNested$1) true
Run Code Online (Sandbox Code Playgroud)
有关进一步的讨论,请参阅此文章:关于Java中私有静态嵌套类的合成访问器的Eclipse警告?
编辑:有趣的是,eclipse编译器与javac的做法不同.使用eclipse时,它会添加内部类本身类型的参数:
a.TestNested$A(a.TestNested) false
private a.TestNested$A(a.TestNested,int) false
a.TestNested$A(a.TestNested,int,a.TestNested$A) true
Run Code Online (Sandbox Code Playgroud)
我试图通过提前暴露该构造函数来提升它:
class A {
A() {
}
private A(int a) {
}
A(int a, A another) { }
}
Run Code Online (Sandbox Code Playgroud)
它通过简单地向合成构造函数添加另一个参数来解决这个问题:
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
Run Code Online (Sandbox Code Playgroud)
首先,感谢您提出这个有趣的问题.我很好奇,以至于我无法抗拒看看字节码.这是以下字节码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
}
Run Code Online (Sandbox Code Playgroud)
如您所见,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 {
}
Run Code Online (Sandbox Code Playgroud)
奇怪 - 我原本以为这个班级实际上会做点什么.为了理解它,让我们看一下构造函数a.TestNested$A:class a.TestNested $ A {final a.TestNested this $ 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
}
Run Code Online (Sandbox Code Playgroud)
查看包可见的构造函数a.TestNested$A(a.TestNested, int, a.TestNested$1),我们可以看到第三个参数被忽略.
现在我们可以解释构造函数和匿名内部类.为了避免私有构造函数的可见性限制,需要额外的构造函数.这个额外的构造函数只是委托给私有构造函数.但是,它不能与私有构造函数具有完全相同的签名.因此,添加匿名内部类以提供唯一的签名,而不会与其他可能的重载构造函数(例如带签名(int,int)或的构造函数)发生冲突(int,Object).由于这个匿名内部类只需要创建一个唯一的签名,因此不需要实例化它,也不需要有内容.