Search code examples
javacastingclassloadermethodhandleinvokevirtual

InvokeExact on the object, whose type is dynamically loaded by classloader


I have spend whole day on this problem. My problem is how to make an MethodHandle.invokeExact invocation on an instance, whose class type is dynamically loaded at program runtime. To make problem more clear, i show my sample code below:

Class<?> expClass = new MyClassLoader().load(....)

//expClass is AddSample.class which is subclass of BaseTemplate         
 BaseTemplate obj = expClass.getConstructor(...)
                .newInstance(...);
MethodHandle myMH = MethodHandles.lookup().findVirtual(expClass, methodName,..);
System.out.println("Object type "+obj.getClass()); //Print AddSample

// If obj is declared as "AddSample obj", the runtime would be OK.
assertEquals((int)myMH.invokeExact(obj,"addintaasdsa" , 10 , 20.0f), 12);

In this sample, expClass is dynamically loaded and its class type is AddSample. The obj instance in the next line is declared to be BaseTemplate, and its real type is AddSample. Class AddSample is subclass of BaseTemplate. Then a MethodHandle myMh is created to the add function of AddSample but the invocation of myMH fails because of receiverType does not match.

The myMH.invokeExact raises runtime error

java.lang.invoke.WrongMethodTypeException: expected (AddSample,String,int,float)int but found (Object,String,int,float)int

because the receiver of this myMH is declared to be on expClass (AddSample), but the receiver obj current provided is declared BaseTemaplte, though the obj's Class is AddSample. InvokeExact requires exact parameter match.


My problem might be simplified as: how to cast an instance from its base type to a child type which is dynamically loaded?

BaseTemplate obj = ...
Class<?> newType = Class('AddSample') //dynamic loaded...

change obj's declared type to AddSample which is dynamically loaded..?

UPDATE:

Class<T> expClass = (Class<T>) new MyClassLoader().run(className, methodName, b);
BaseTemplate obj = ..
Class<T> newType = (Class<T>) obj.getClass().getClassLoader().loadClass("AddSample");
T tObj = newType.cast(obj);
assertEquals((int)myMH.invokeExact(tObj,"addintaasdsa" , 10 , 20.0f), 12);

Using cast does not help fix the problem which is the same previous result. The reason is still that the given parameter does not exact match myMH declaration. It would be more clear when i check the generated bytecodes:

L23  # For cast 
    LINENUMBER 126 L23
    ALOAD 10: newType
    ALOAD 8: obj
    INVOKEVIRTUAL Class.cast (Object) : Object  #tObj is Object and its real type is AddSample here
    ASTORE 11
   L24
    LINENUMBER 128 L24
    ALOAD 9: myMH           # Push myMH to stack
    ALOAD 11: tObj          # Push tObj to Stack. tObj is declared Object type and its real type is AddSample. 
    LDC "addintaasdsa"      #Push String to Stack
    BIPUSH 10               #Push int to Stacl
    LDC 20.0                #Push float to Stack 
    INVOKEVIRTUAL MethodHandle.invokeExact (Object, String, int, float) : int

the myMH points to (AddSample,String,int,float)int, but given parameters: (Object, String, int, float), and this result in runtime error which I was shown previously.

Thanks


Solution

  • You can’t use invokeExact if the compile-time type of an argument doesn’t match the MethodHandle’s parameter type. It doesn’t help to play around with the Generic constructs like invoking cast on a Class<T>, the dynamic type is still unknown to the compiler.

    Or, in other words, due to type erasure, the type of tObj is still Object on the byte code level. And this is the “invoked type” of the MethodHandle.

    The simplest solution is to use invoke rather than invokeExact.

    The only thing you can do if you want to use invokeExact, is to transform the MethodHandle to the type which you will eventually invoke, i.e. change the type of the first parameter to Object:

    myMH=myMH.asType(myMH.type().changeParameterType(0, Object.class));
    // now it doesn’t matter that obj has compile-time type Object
    assertEquals((int)myMH.invokeExact(obj, "addintaasdsa", 10, 20.0f), 12);