InvokeExact для объекта, тип которого динамически загружается загрузчиком классов
Я потратил целый день на эту проблему. Моя проблема заключается в том, как сделать метод MethodHandle.invokeExact для экземпляра, тип класса которого динамически загружается во время выполнения программы. Чтобы сделать проблему более ясной, я показываю мой пример кода ниже:
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);
В этом примере expClass загружается динамически, и его тип класса AddSample
, Экземпляр obj в следующей строке объявляется как BaseTemplate, а его реальный тип AddSample
, Класс AddSample является подклассом BaseTemplate. Затем MethodHandle myMh создается для функции добавления AddSample
но вызов myMH терпит неудачу, потому что receiveType не совпадает.
myMH.invokeExact
вызывает ошибку во время выполнения
java.lang.invoke.WrongMethodTypeException: expected (AddSample,String,int,float)int but found (Object,String,int,float)int
потому что получатель этого myMH
объявляется как expClass (AddSample), но получатель obj
текущий предоставленный объявлен BaseTemaplte, хотя класс объекта является AddSample. InvokeExact требует точного соответствия параметров.
Моя проблема может быть упрощена как: как привести экземпляр из его базового типа к дочернему типу, который динамически загружается?
BaseTemplate obj = ...
Class<?> newType = Class('AddSample') //dynamic loaded...
изменить объявленный тип obj на AddSample, который динамически загружается..?
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);
Использование броска не помогает решить проблему, которая является тем же самым предыдущим результатом. Причина по-прежнему заключается в том, что данный параметр не точно соответствует объявлению myMH. Было бы более понятно, когда я проверяю сгенерированные байт-коды:
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
myMH указывает на (AddSample,String,int,float)int
, но с учетом параметров: (Object, String, int, float)
, и это приводит к ошибке во время выполнения, которая мне была показана ранее.
Спасибо
2 ответа
Вы не можете использовать invokeExact
если тип аргумента во время компиляции не соответствует MethodHandle
Тип параметра. Это не помогает поиграться с общими конструкциями, такими как вызов cast
на Class<T>
динамический тип все еще неизвестен компилятору.
Или, другими словами, из-за стирания типа, типа tObj
все еще Object
на уровне байтового кода. И это "вызываемый тип" MethodHandle
,
Самое простое решение - использовать invoke
скорее, чем invokeExact
,
Единственное, что вы можете сделать, если хотите использовать invokeExact
, чтобы преобразовать MethodHandle
к типу, который вы в конечном итоге будете вызывать, т.е. измените тип первого параметра на 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);
Чтобы иметь смысл, вам нужно набрать метод (иначе приведение будет бессмысленным):
public <T> void doSomething() {
BaseTemplate obj = ...
Class<T> newType = Class('AddSample');
T t = newType.cast(obj);
Без типизации метода вы не можете связать динамический класс с типом, который будет приведен к типу.