3

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

shijie xu
  • 1,975
  • 21
  • 52

2 Answers2

2

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);
Holger
  • 285,553
  • 42
  • 434
  • 765
  • great, this makes perfect sense when you understand type erasure, but I agree it's a bit elusive when you are just looking at the code trying to understand why your invokeExact isn't working – Kyle C Apr 17 '17 at 19:48
  • I hear invokeExact is 20X faster than invoke. will that be compromised with this approach? – user1870400 May 02 '19 at 09:52
  • @user1870400 not all environments are subject to such performance differences between `invoke` and `invokeExact`, but to my experience, those affected by it are much faster when using `invokeExact` on an adapted method handle, like shown at the end of this answer, than using `invoke` on the original handle. – Holger May 02 '19 at 10:18
  • Got the answer! But also curious to know what do you mean by “environment” here? Different jvms you mean? I am using OpenJdk 11. Cpu instruction set is x86 – user1870400 May 02 '19 at 11:14
  • For this specific issue, the JVM implementation, the class library (namely the `java.lang.invoke` package), and the particular JVM startup options may be relevant. – Holger May 02 '19 at 12:25
0

To make sense, you'll have to type the method ( otherwise the cast is pointless):

public <T> void doSomething() {

BaseTemplate obj = ...
Class<T> newType = Class('AddSample');
T t = newType.cast(obj);

Without the method typing, you can't tie the dynamic class to the type to be cast to.

Bohemian
  • 412,405
  • 93
  • 575
  • 722
  • I update my code but `newType.cast` fails. I am not sure whether i have understand your answer. – shijie xu Feb 14 '15 at 13:31
  • How does it fail? If it's a ClassCastException then the object was not of the type you expected. – Bohemian Feb 14 '15 at 13:42
  • My code and exception are appended to the bottom of first post. The type of the variable `obj` is already `AddSample` though it is declared to be `BaseTemplate`. It reports exception because of casting from `AddSample` to `AddSample` (Class AddSample extends from BaseTemplate). Did I understand your suggestion correct? ? – shijie xu Feb 14 '15 at 13:53
  • 1
    For classes to be equal, they not only have to have the same name but be loaded by the same class loader. It could be that you instance's class was was loaded by a different class than `Class`. Try this: `Class newType = obj.getClass().getClassLoader().loadClass("AddSample");` to force the dynamic class to be loaded using the same loader as the instance's class – Bohemian Feb 14 '15 at 14:17
  • This eliminates cast error, but still have not solved my issue for invokeExact problem. I updated to my original post.. – shijie xu Feb 14 '15 at 15:16