是否可以在现有实例上调用构造函数?
Is it possible to call constructor on existing instance?
众所周知,使用 sun.misc.Unsafe#allocateInstance
可以在不调用任何 class 构造函数的情况下创建对象。
是否可以反其道而行之:给定一个现有实例,对其调用构造函数?
澄清:这不是关于我在生产代码中做的事情的问题。我很好奇 JVM 内部结构和仍然可以完成的疯狂事情。欢迎回答特定于某些 JVM 版本的问题。
构造函数不是实例方法,因此不能在实例上调用构造函数。
如果您查看反射库,您会看到 Class.getConstructor()
is Constructor
, which doesn't have any methods that can accept a instance - its only relevant method is newInstance()
的 return 类型不接受目标实例;它创造了一个。
另一方面,Class.getMethod()
is Method
的return类型,第一个参数是实例。
Constructor
不是 Method
。
Constructors are invoked by class instance creation expressions (§15.9), by the conversions and concatenations caused by the string concatenation operator +(§15.18.1), and by explicit constructor invocations from other constructors (§8.8.7).
...
Constructors are never invoked by method invocation expressions (§15.12).
所以不,这是不可能的。
如果您想在构造函数和其他地方执行一些通用操作,请将其放入一个方法中,然后从构造函数中调用它。
An invokespecial
instruction is type safe iff all of the following are true:
... (Stuff about non-init methods)
- MethodName is
<init>
.
- Descriptor specifies a void return type.
- One can validly pop types matching the argument types given in Descriptor and an uninitialized type, UninitializedArg, off the incoming operand stack, yielding OperandStack.
- ...
如果您已经初始化了实例,则它不是未初始化的类型,因此这将失败。
请注意,其他 invoke*
指令(invokevirtual
、invokeinterface
、invokestatic
、invokedynamic
)明确排除了对 <init>
方法的调用,所以 invokespecial
是调用它们的唯一方法。
JVMS §2.9 禁止在已经初始化的对象上调用构造函数:
Instance initialization methods may be invoked only within the Java
Virtual Machine by the invokespecial instruction, and
they may be invoked only on uninitialized class instances.
但是,技术上仍然可以使用JNI调用已初始化对象的构造函数。 CallVoidMethod 函数在 <init>
和普通 Java 方法之间没有区别。此外,JNI 规范提示 CallVoidMethod
可以 用于调用构造函数,尽管它没有说明实例是否必须初始化:
When these functions are used to call private methods and constructors, the method ID must be derived from the real class of obj, not from one of its superclasses.
我已经验证以下代码在 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
}
}
众所周知,使用 sun.misc.Unsafe#allocateInstance
可以在不调用任何 class 构造函数的情况下创建对象。
是否可以反其道而行之:给定一个现有实例,对其调用构造函数?
澄清:这不是关于我在生产代码中做的事情的问题。我很好奇 JVM 内部结构和仍然可以完成的疯狂事情。欢迎回答特定于某些 JVM 版本的问题。
构造函数不是实例方法,因此不能在实例上调用构造函数。
如果您查看反射库,您会看到 Class.getConstructor()
is Constructor
, which doesn't have any methods that can accept a instance - its only relevant method is newInstance()
的 return 类型不接受目标实例;它创造了一个。
另一方面,Class.getMethod()
is Method
的return类型,第一个参数是实例。
Constructor
不是 Method
。
Constructors are invoked by class instance creation expressions (§15.9), by the conversions and concatenations caused by the string concatenation operator +(§15.18.1), and by explicit constructor invocations from other constructors (§8.8.7).
...
Constructors are never invoked by method invocation expressions (§15.12).
所以不,这是不可能的。
如果您想在构造函数和其他地方执行一些通用操作,请将其放入一个方法中,然后从构造函数中调用它。
An
invokespecial
instruction is type safe iff all of the following are true:... (Stuff about non-init methods)
- MethodName is
<init>
.- Descriptor specifies a void return type.
- One can validly pop types matching the argument types given in Descriptor and an uninitialized type, UninitializedArg, off the incoming operand stack, yielding OperandStack.
- ...
如果您已经初始化了实例,则它不是未初始化的类型,因此这将失败。
请注意,其他 invoke*
指令(invokevirtual
、invokeinterface
、invokestatic
、invokedynamic
)明确排除了对 <init>
方法的调用,所以 invokespecial
是调用它们的唯一方法。
JVMS §2.9 禁止在已经初始化的对象上调用构造函数:
Instance initialization methods may be invoked only within the Java Virtual Machine by the invokespecial instruction, and they may be invoked only on uninitialized class instances.
但是,技术上仍然可以使用JNI调用已初始化对象的构造函数。 CallVoidMethod 函数在 <init>
和普通 Java 方法之间没有区别。此外,JNI 规范提示 CallVoidMethod
可以 用于调用构造函数,尽管它没有说明实例是否必须初始化:
When these functions are used to call private methods and constructors, the method ID must be derived from the real class of obj, not from one of its superclasses.
我已经验证以下代码在 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
}
}