是否可以在现有实例上调用构造函数?

时间:2018-02-05 05:59:10

标签: java reflection jvm unsafe

众所周知,使用sun.misc.Unsafe#allocateInstance可以在不调用任何类构造函数的情况下创建对象。

是否可以执行相反的操作:给定现有实例,在其上调用构造函数?

澄清:这不是我在生产代码中所做的事情的问题。我很好奇JVM内部和疯狂的事情仍然可以完成。欢迎特定于某些JVM版本的答案。

4 个答案:

答案 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)

JVM spec for invokespecial

  

如果满足以下所有条件,则invokespecial指令是类型安全的:

     

...(关于非初始化方法的东西)

     
      
  • MethodName为<init>
  •   
  • Descriptor指定void返回类型。
  •   
  • 可以有效地弹出类型,匹配Descriptor中给出的参数类型和未初始化的类型UninitializedArg,关闭传入操作数堆栈,产生OperandStack。
  •   
  • ...
  •   

如果您已经初始化了实例,那么它不是未初始化的类型,因此会失败。

请注意,其他invoke*说明(invokevirtualinvokeinterfaceinvokestaticinvokedynamic)明确排除了<init>方法的调用,因此{ {1}}是调用它们的唯一方法。

答案 3 :(得分:0)

来自JLS Sec 8.8

  

构造函数由类实例创建表达式(第15.9节),由字符串连接运算符+(第15.18.1节)引起的转换和连接以及来自其他构造函数的显式构造函数调用(第8.8.7节)调用。

     

...

     

方法调用表达式(第15.12节)永远不会调用构造函数。

所以不,这是不可能的。

如果您想在构造函数和其他地方执行某些常见操作,请将其放入方法中,然后从构造函数中调用它。