Is it possible to call constructor on existing instance?

非 Y 不嫁゛ 提交于 2019-12-08 10:52:45

问题


It is known that using sun.misc.Unsafe#allocateInstance one can create an object without calling any class constructors.

Is it possible to do the opposite: given an existing instance, invoke a constructor on it?


Clarification: this is not the question about something I'd do in production code. I'm curious about JVM internals and crazy things that can still be done. Answers specific to some JVM version are welcome.


回答1:


JVMS §2.9 forbids invocation of constructor on already initialized objects:

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.

However, it is still technically possible to invoke constructor on initialized object with JNI. CallVoidMethod function does not make difference between <init> and ordinary Java methods. Moreover, JNI specification hints that CallVoidMethod may be used to call a constructor, though it does not say whether an instance has to be initialized or not:

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.

I've verified that the following code works both in JDK 8 and JDK 9. JNI allows you to do unsafe things, but you should not rely on this in production applications.

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
    }
}



回答2:


A constructor is not an instance method, so no you can't invoke a constructor on an instance.

If you look at the reflection library, you'll see that the return type of Class.getConstructor() is Constructor, which doesn't have any methods that can accept a instance - its only relevant method is newInstance(), which doesn't accept a target instance; it creates one.

On the other hand, the return type of Class.getMethod() is Method, whose first parameter is the instance.

A Constructor is not a Method.




回答3:


In the JVM spec for invokespecial:

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.
  • ...

If you've already initialized the instance, it's not an uninitialized type, so this will fail.

Note that other invoke* instructions (invokevirtual, invokeinterface, invokestatic, invokedynamic) explicitly preclude invocation of <init> methods, so invokespecial is the only way to invoke them.




回答4:


From JLS Sec 8.8

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).

So no, it's not possible.

If there is some common action you want to take in the constructor and elsewhere, put it into a method, and invoke that from the constructor.



来源:https://stackoverflow.com/questions/48616630/is-it-possible-to-call-constructor-on-existing-instance

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!