为什么具有静态嵌套 class 会导致在不在源代码中时添加第二个构造函数?
Why does having a static nested class cause a second constructor to be added when it's not in the source?
我试图使用反射来破坏 BillPaugh Singleton 解决方案,我能够做到,但是在访问 BillPaughSingleTon 解决方案时我可以看到两个构造函数。为什么这样 ?同样通过反复试验发现 HelperClass 内部的行导致了这一点。又是为什么?
BillPaughClass
package creational.BillPaugh;
public class SingleTonBillPaugh
{
private SingleTonBillPaugh instance;
public static SingleTonBillPaugh getInstance()
{
return SingleTonHelper.instance;
}
private SingleTonBillPaugh()
{
System.out.println(Thread.currentThread().getName() + " instance is going to be created");
}
static class SingleTonHelper
{
private static final SingleTonBillPaugh instance = new SingleTonBillPaugh(); //if we remove this line, multiple constructor will not be there. But this line is needed for singleton.
}
}
使用反射打破 SingleTon。
package creational.BillPaugh;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class BreakBillPaughUsingReflection
{
public static void main(String[] args)
{
SingleTonBillPaugh singletonInstance1 = SingleTonBillPaugh.getInstance();
System.out.println("singletonInstance1 " + singletonInstance1);
SingleTonBillPaugh singletonInstance2;
Constructor[] constructors = SingleTonBillPaugh.class.getDeclaredConstructors();
for (Constructor construct : constructors)
{
construct.setAccessible(true);
try
{
singletonInstance2 = (SingleTonBillPaugh) construct.newInstance();
System.out.println("singletonInstance2 " + singletonInstance2);
}
catch (InstantiationException | IllegalAccessException | IllegalArgumentException
| InvocationTargetException e)
{
e.printStackTrace();
}
}// end for
Constructor[] constructors2 = NormalClass.class.getDeclaredConstructors();
}
}
两个构造函数的证明。
这是我的看法:
因为源码中的SingleTonBillPaugh
构造函数是private
,SingleTonBillPaugh$SingleTonHelper
访问不到,所以编译器生成了一个合成构造函数SingleTonBillPaugh$SingleTonHelper
可以访问。这就是合成方法和构造函数的用途:提供一种访问包含 class 的私有数据的方法。
对我来说更大的问题是为什么合成构造函数接受一个参数,为什么参数的类型是 SingleTonBillPaugh
? (你的屏幕截图让它看起来像是一个 SingleTonBillPaugh
实例,但在我的测试中它实际上是一个 SingleTonBillPaugh
实例——也就是说,除了 [=15] 之外,这里还生成了第三个 class =] 和 SingleTonBillPaugh$SingleTonHelper
).
我对 那个 问题的回答是:因为否则,将有两个构造函数的区别仅在于一个是合成的并且可由 SingleTonBillPaugh$SingleTonHelper
访问,另一个不是。 Java 要求签名与此不同,因此它生成 class 的唯一原因是将合成构造函数与非合成构造函数区分开来。
我们可以看到我们确实有一个 SingleTonBillPaugh
class,如果我们 javap -p -c SingleTonBillPaugh$1
它,我们得到:
class SingleTonBillPaugh {
}
没有比这更小的了,这表明它确实纯粹充当合成构造函数的参数类型。我们可以通过使用 javap -p -c SingleTonBillPaugh$SingleTonHelper
:
查看 SingleTonBillPaugh$SingleTonHelper
的字节码来进一步确认
class SingleTonBillPaugh$SingleTonHelper {
private static final SingleTonBillPaugh instance;
SingleTonBillPaugh$SingleTonHelper();
Code:
0: aload_0
1: invokespecial #2 // Method java/lang/Object."<init>":()V
4: return
static SingleTonBillPaugh access[=11=]0();
Code:
0: getstatic #1 // Field instance:LSingleTonBillPaugh;
3: areturn
static {};
Code:
0: new #3 // class SingleTonBillPaugh
3: dup
4: aconst_null
5: invokespecial #4 // Method SingleTonBillPaugh."<init>":(LSingleTonBillPaugh;)V
8: putstatic #1 // Field instance:LSingleTonBillPaugh;
11: return
}
注意它是如何(就在最后)调用构造函数的单参数版本(传入 null
)。
事实上,它似乎总是这样做——将一个新参数添加到构造函数参数列表的末尾。如果我更改私有构造函数以接受 String
并更新 SingleTonHelper
以传递它 ""
,则合成构造函数最终为 SingleTonBillPaugh(String, SingleTonBillPaugh)
.
在下面回复你的问题:
I kept one sysout in no argument constructor and when inner class called no argument constructor of outer class (which supposedly be synthetic constructor) the same sysout is printed. Why so? is it because internally synthetic constructor calls my provided pvt constructor?
没错,合成构造函数调用了私有构造函数。像这样的时候,很高兴深入了解字节码:
这是我的 SingleTonBillPaugh.java
副本:
public class SingleTonBillPaugh
{
public static SingleTonBillPaugh getInstance()
{
return SingleTonHelper.instance;
}
private SingleTonBillPaugh()
{
System.out.println(Thread.currentThread().getName() + " instance is going to be created");
}
static class SingleTonHelper
{
private static final SingleTonBillPaugh instance = new SingleTonBillPaugh();
}
}
如果我们编译它,然后使用 javap -p -c SingleTonBillPaugh
,我们得到:
public class SingleTonBillPaugh {
public static SingleTonBillPaugh getInstance();
Code:
0: invokestatic #2 // Method SingleTonBillPaugh$SingleTonHelper.access[=13=]0:()LSingleTonBillPaugh;
3: areturn
private SingleTonBillPaugh();
Code:
0: aload_0
1: invokespecial #3 // Method java/lang/Object."<init>":()V
4: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
7: new #5 // class java/lang/StringBuilder
10: dup
11: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V
14: invokestatic #7 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
17: invokevirtual #8 // Method java/lang/Thread.getName:()Ljava/lang/String;
20: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
23: ldc #10 // String instance is going to be created
25: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
28: invokevirtual #11 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
31: invokevirtual #12 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
34: return
SingleTonBillPaugh(SingleTonBillPaugh);
Code:
0: aload_0
1: invokespecial #1 // Method "<init>":()V
4: return
}
正如我们所见,SingleTonBillPaugh(SingleTonBillPaugh)
构造函数,以源代码形式编写,基本上是:
SingleTonBillPaugh(SingleTonBillPaugh unused) {
this();
}
我试图使用反射来破坏 BillPaugh Singleton 解决方案,我能够做到,但是在访问 BillPaughSingleTon 解决方案时我可以看到两个构造函数。为什么这样 ?同样通过反复试验发现 HelperClass 内部的行导致了这一点。又是为什么?
BillPaughClass
package creational.BillPaugh;
public class SingleTonBillPaugh
{
private SingleTonBillPaugh instance;
public static SingleTonBillPaugh getInstance()
{
return SingleTonHelper.instance;
}
private SingleTonBillPaugh()
{
System.out.println(Thread.currentThread().getName() + " instance is going to be created");
}
static class SingleTonHelper
{
private static final SingleTonBillPaugh instance = new SingleTonBillPaugh(); //if we remove this line, multiple constructor will not be there. But this line is needed for singleton.
}
}
使用反射打破 SingleTon。
package creational.BillPaugh;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class BreakBillPaughUsingReflection
{
public static void main(String[] args)
{
SingleTonBillPaugh singletonInstance1 = SingleTonBillPaugh.getInstance();
System.out.println("singletonInstance1 " + singletonInstance1);
SingleTonBillPaugh singletonInstance2;
Constructor[] constructors = SingleTonBillPaugh.class.getDeclaredConstructors();
for (Constructor construct : constructors)
{
construct.setAccessible(true);
try
{
singletonInstance2 = (SingleTonBillPaugh) construct.newInstance();
System.out.println("singletonInstance2 " + singletonInstance2);
}
catch (InstantiationException | IllegalAccessException | IllegalArgumentException
| InvocationTargetException e)
{
e.printStackTrace();
}
}// end for
Constructor[] constructors2 = NormalClass.class.getDeclaredConstructors();
}
}
两个构造函数的证明。
这是我的看法:
因为源码中的SingleTonBillPaugh
构造函数是private
,SingleTonBillPaugh$SingleTonHelper
访问不到,所以编译器生成了一个合成构造函数SingleTonBillPaugh$SingleTonHelper
可以访问。这就是合成方法和构造函数的用途:提供一种访问包含 class 的私有数据的方法。
对我来说更大的问题是为什么合成构造函数接受一个参数,为什么参数的类型是 SingleTonBillPaugh
? (你的屏幕截图让它看起来像是一个 SingleTonBillPaugh
实例,但在我的测试中它实际上是一个 SingleTonBillPaugh
实例——也就是说,除了 [=15] 之外,这里还生成了第三个 class =] 和 SingleTonBillPaugh$SingleTonHelper
).
我对 那个 问题的回答是:因为否则,将有两个构造函数的区别仅在于一个是合成的并且可由 SingleTonBillPaugh$SingleTonHelper
访问,另一个不是。 Java 要求签名与此不同,因此它生成 class 的唯一原因是将合成构造函数与非合成构造函数区分开来。
我们可以看到我们确实有一个 SingleTonBillPaugh
class,如果我们 javap -p -c SingleTonBillPaugh$1
它,我们得到:
class SingleTonBillPaugh {
}
没有比这更小的了,这表明它确实纯粹充当合成构造函数的参数类型。我们可以通过使用 javap -p -c SingleTonBillPaugh$SingleTonHelper
:
SingleTonBillPaugh$SingleTonHelper
的字节码来进一步确认
class SingleTonBillPaugh$SingleTonHelper {
private static final SingleTonBillPaugh instance;
SingleTonBillPaugh$SingleTonHelper();
Code:
0: aload_0
1: invokespecial #2 // Method java/lang/Object."<init>":()V
4: return
static SingleTonBillPaugh access[=11=]0();
Code:
0: getstatic #1 // Field instance:LSingleTonBillPaugh;
3: areturn
static {};
Code:
0: new #3 // class SingleTonBillPaugh
3: dup
4: aconst_null
5: invokespecial #4 // Method SingleTonBillPaugh."<init>":(LSingleTonBillPaugh;)V
8: putstatic #1 // Field instance:LSingleTonBillPaugh;
11: return
}
注意它是如何(就在最后)调用构造函数的单参数版本(传入 null
)。
事实上,它似乎总是这样做——将一个新参数添加到构造函数参数列表的末尾。如果我更改私有构造函数以接受 String
并更新 SingleTonHelper
以传递它 ""
,则合成构造函数最终为 SingleTonBillPaugh(String, SingleTonBillPaugh)
.
在下面回复你的问题:
I kept one sysout in no argument constructor and when inner class called no argument constructor of outer class (which supposedly be synthetic constructor) the same sysout is printed. Why so? is it because internally synthetic constructor calls my provided pvt constructor?
没错,合成构造函数调用了私有构造函数。像这样的时候,很高兴深入了解字节码:
这是我的 SingleTonBillPaugh.java
副本:
public class SingleTonBillPaugh
{
public static SingleTonBillPaugh getInstance()
{
return SingleTonHelper.instance;
}
private SingleTonBillPaugh()
{
System.out.println(Thread.currentThread().getName() + " instance is going to be created");
}
static class SingleTonHelper
{
private static final SingleTonBillPaugh instance = new SingleTonBillPaugh();
}
}
如果我们编译它,然后使用 javap -p -c SingleTonBillPaugh
,我们得到:
public class SingleTonBillPaugh {
public static SingleTonBillPaugh getInstance();
Code:
0: invokestatic #2 // Method SingleTonBillPaugh$SingleTonHelper.access[=13=]0:()LSingleTonBillPaugh;
3: areturn
private SingleTonBillPaugh();
Code:
0: aload_0
1: invokespecial #3 // Method java/lang/Object."<init>":()V
4: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
7: new #5 // class java/lang/StringBuilder
10: dup
11: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V
14: invokestatic #7 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
17: invokevirtual #8 // Method java/lang/Thread.getName:()Ljava/lang/String;
20: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
23: ldc #10 // String instance is going to be created
25: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
28: invokevirtual #11 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
31: invokevirtual #12 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
34: return
SingleTonBillPaugh(SingleTonBillPaugh);
Code:
0: aload_0
1: invokespecial #1 // Method "<init>":()V
4: return
}
正如我们所见,SingleTonBillPaugh(SingleTonBillPaugh)
构造函数,以源代码形式编写,基本上是:
SingleTonBillPaugh(SingleTonBillPaugh unused) {
this();
}