众所周知,使用sun.misc.Unsafe#allocateInstance
可以在不调用任何类构造函数的情况下创建对象。
是否可以执行相反的操作:给定现有实例,在其上调用构造函数?
澄清:这不是我在生产代码中所做的事情的问题。我很好奇JVM内部和疯狂的事情仍然可以完成。欢迎特定于某些JVM版本的答案。
答案 0 :(得分:3)
JVMS §2.9禁止在已初始化的对象上调用构造函数:
只能在Java中调用实例初始化方法 通过invokespecial指令的虚拟机,和 它们只能在未初始化的类实例上调用。
但是,使用 JNI 在初始化对象上调用构造函数仍然技术上可行。 CallVoidMethod函数在<init>
和普通Java方法之间没有区别。此外,JNI规范暗示CallVoidMethod
可能用于调用构造函数,但它没有说明是否必须初始化实例:
当这些函数用于调用私有方法和构造函数时,方法ID必须从obj的真实类派生,而不是从其超类之一派生。< / p>
我已经验证以下代码在JDK 8和JDK 9中都有效.JNI允许您执行不安全的操作,但不应该依赖来生成应用程序。
<强> ConstructorInvoker.java 强>
public class ConstructorInvoker {
static {
System.loadLibrary("constructorInvoker");
}
public static native void invoke(Object instance);
}
<强> constructorInvoker.c 强>
#include <jni.h>
JNIEXPORT void JNICALL
Java_ConstructorInvoker_invoke(JNIEnv* env, jclass self, jobject instance) {
jclass cls = (*env)->GetObjectClass(env, instance);
jmethodID constructor = (*env)->GetMethodID(env, cls, "<init>", "()V");
(*env)->CallVoidMethod(env, instance, constructor);
}
<强> TestObject.java 强>
public class TestObject {
int x;
public TestObject() {
System.out.println("Constructor called");
x++;
}
public static void main(String[] args) {
TestObject obj = new TestObject();
System.out.println("x = " + obj.x); // x = 1
ConstructorInvoker.invoke(obj);
System.out.println("x = " + obj.x); // x = 2
}
}
答案 1 :(得分:2)
构造函数不是实例方法,因此不能在实例上调用构造函数。
如果你查看反射库,你会发现Class.getConstructor()
的返回类型是Constructor
,它没有任何可以接受实例的方法 - 只有相关方法是newInstance()
,它不接受目标实例;它创造了一个。
另一方面,Class.getMethod()
的返回类型为Method
,其第一个参数是实例。
Constructor
不是Method
。
答案 2 :(得分:1)
如果满足以下所有条件,则
invokespecial
指令是类型安全的:...(关于非初始化方法的东西)
- MethodName为
<init>
。- Descriptor指定void返回类型。
- 可以有效地弹出类型,匹配Descriptor中给出的参数类型和未初始化的类型UninitializedArg,关闭传入操作数堆栈,产生OperandStack。
- ...
如果您已经初始化了实例,那么它不是未初始化的类型,因此会失败。
请注意,其他invoke*
说明(invokevirtual
,invokeinterface
,invokestatic
,invokedynamic
)明确排除了<init>
方法的调用,因此{ {1}}是调用它们的唯一方法。
答案 3 :(得分:0)
构造函数由类实例创建表达式(第15.9节),由字符串连接运算符+(第15.18.1节)引起的转换和连接以及来自其他构造函数的显式构造函数调用(第8.8.7节)调用。
...
方法调用表达式(第15.12节)永远不会调用构造函数。
所以不,这是不可能的。
如果您想在构造函数和其他地方执行某些常见操作,请将其放入方法中,然后从构造函数中调用它。