- Получение Method из Method Reference в Java
- Зачем вообще это нужно?
- SerializedLambda
- А с конструкторами работает?
- Совместимость
- Готовый утилитарный метод
- Get the declared method by name and parameter type in Java
- Example
- Output
- Invoke a method using reflection java code example
- How to Invoke Method by Name in Java Dynamically Using Reflection?
- Java
- Java
- How to invoke a method which throws an Exception using Java Reflection?
- Java Reflection to call methods with multiple parameters
Получение Method из Method Reference в Java
вызываемый метод класса (или хотя бы его имя), т.е. в примере это java.lang.String.length() . Как выяснилось, не одного меня волновал этот вопрос, нашлись такие обсуждения на stackoverflow [1], [2], проекты на GitHub, которые так или иначе касаются этой проблемы [1], [2], [3], но не один из них не дает ровно то, что нужно. На Хабре ibessonov предложил свое решение, а apangin — свое в комментарии. Вариант Андрея мне понравился, но вскоре выяснились некоторые связанные с ним проблемы. Во-первых, это основано на внутренних классах JDK, все это работает только в Java 8, но не в более поздних версиях. Во-вторых, этот метод имеет ряд ограничений, например для BiFunction или BiConsumer он выдает неверное значение (это как раз обсуждается в комментариях).
В общем, перебрав несколько вариантов, удалось найти тот, который не имеет этих изьянов и работает в поздних версиях JDK — SerializedLambda. Забегая вперед сразу скажу, что это работает только с функциональными интерфейсами, помеченными как java.io.Serializable (т.е. с java.util.function.Function работать не будет), но в целом это не проблемное ограничение.
Зачем вообще это нужно?
Перед тем как перейти к решению, отвечу на резонный вопрос а зачем это может понадобиться?
В моем случае это используется в тестовых фреймворках, чтобы добавить диагностическую информацию о вызываемом методе, который определялся через method reference. Это позволяет сделать одновременно лаконичный и compile-безопасный код. Например, можно сделать «where» hamcrest matcher, использующий функцию-extractor для значений:
List list = Arrays.asList( new SamplePojo() .setName("name1"), new SamplePojo() .setName("name2") ); // success assertThat(list, hasItem(where(SamplePojo::getName, equalTo("name1")))); // fails with diagnostics: // java.lang.AssertionError: // Expected: every item is Object that matches "name1" after call SamplePojo.getName // but: an item was "name2" assertThat(list, everyItem(where(SamplePojo::getName, equalTo("name1"))));
Обратите внимание, что в случае ошибки тест упадет с диагностикой «after call SamplePojo.getName» . Для однострочного теста это кажется избыточным, но hamcrest-выражения могут иметь многоуровневую вложенность, поэтому лишняя детализация не помешает.
SerializedLambda
Класс java.lang.invoke.SerializedLambda — это сериализованное представление лямбда-выражения. Важно уточнить, что интерфейс лямбда-выражения должен быть помечен как Serializable :
@FunctionalInterface public interface ThrowingFunction extends java.io.Serializable
Как следует из javadoc класса, чтобы получить объект SerializedLambda , следует вызвать приватный writeReplace на лямбда-объекте:
@Nullable private static SerializedLambda getSerializedLambda(Serializable lambda) < for (Classcl = lambda.getClass(); cl != null; cl = cl.getSuperclass()) < try < Method m = cl.getDeclaredMethod("writeReplace"); m.setAccessible(true); Object replacement = m.invoke(lambda); if (!(replacement instanceof SerializedLambda)) < break; >return (SerializedLambda) replacement; > catch (NoSuchMethodException e) < // skip, continue >catch (IllegalAccessException | InvocationTargetException | SecurityException e) < throw new IllegalStateException("Failed to call writeReplace", e); >> return null; >
Дальше все нужные детали достаем из SerializedLambda , нужно только несложное преобразование. Например, имена классов записаны через «/» (слеш) вместо точек, а примитивные типы сделаны сокращениями. Например getImplMethodSignature() возвращает строку «(Z)V» , это означает один аргумент (внутри скобок) типа boolean ( «Z» ), тип возвращаемого значения — void ( «V» ):
static ClassparseType(String typeName, boolean allowVoid) < if ("Z".equals(typeName)) < return boolean.class; >else if ("B".equals(typeName)) < return byte.class; >else if ("C".equals(typeName)) < return char.class; >else if ("S".equals(typeName)) < return short.class; >else if ("I".equals(typeName)) < return int.class; >else if ("J".equals(typeName)) < return long.class; >else if ("F".equals(typeName)) < return float.class; >else if ("D".equals(typeName)) < return double.class; >else if ("V".equals(typeName)) < if (allowVoid) < return void.class; >else < throw new IllegalStateException("void (V) type is not allowed"); >> else < if (!typeName.startsWith("L")) < throw new IllegalStateException("Wrong format of argument type " + "(should start with 'L'): " + typeName); >String implClassName = typeName.substring(1); return implClassForName(implClassName); > > private static Class >implClassForName(String implClassName) < String className = implClassName.replace('/', '.'); try < return Class.forName(className); >catch (ClassNotFoundException e) < throw new IllegalStateException("Failed to load class " + implClassName, e); >>
Остается только найти подходящий метод в интерфейсе, чтобы вернуть правильный результат:
@Nullable public static Method unreferenceLambdaMethod(Serializable lambda) < SerializedLambda serializedLambda = getSerializedLambda(lambda); if (serializedLambda != null && (serializedLambda.getImplMethodKind() == MethodHandleInfo.REF_invokeVirtual || serializedLambda.getImplMethodKind() == MethodHandleInfo.REF_invokeStatic)) < Classcls = implClassForName(serializedLambda.getImplClass()); Class[] argumentClasses = parseArgumentClasses(serializedLambda.getImplMethodSignature()); return Stream.of(cls.getDeclaredMethods()) .filter(method -> method.getName().equals(serializedLambda.getImplMethodName()) && Arrays.equals(method.getParameterTypes(), argumentClasses)) .findFirst().orElse(null); > return null; >
А с конструкторами работает?
Работает. Тут будет другой тип serializedLambda.getImplMethodKind() .
@Nullable public static Constructor unreferenceLambdaConstructor(Serializable lambda) < SerializedLambda serializedLambda = getSerializedLambda(lambda); if (serializedLambda != null && (serializedLambda.getImplMethodKind() == MethodHandleInfo.REF_newInvokeSpecial)) < Classcls = implClassForName(serializedLambda.getImplClass()); Class[] argumentClasses = parseArgumentClasses(serializedLambda.getImplMethodSignature()); return Stream.of(cls.getDeclaredConstructors()) .filter(constructor -> Arrays.equals(constructor.getParameterTypes(), argumentClasses)) .findFirst().orElse(null); > return null; >
// new Integer(String) ThrowingFunction fun = Integer::new; Constructor constructor = unreferenceLambdaConstructor(fun);
Совместимость
Это работает в Java 8, Java 11, Java 14, не требует внешних библиотек или доступа к приватным api JDK, не требует дополнительных параметров запуска JVM. Кроме того, применимо и к статическим методам и к методам с разным количеством аргументов (т.е. не только Function-подобные).
Единственное неудобство — для каждого вида функций придется создать сериализуемое представление, например:
@FunctionalInterface public interface SerializableBiFunction extends Serializable < R apply(T arg1, U arg2); >// Integer.parseInt(String, int) SerializableBiFunction fun = Integer::parseInt; Method method = unreferenceLambdaMethod(fun);
Полную реализацию можно найти тут.
Готовый утилитарный метод
Вы можете скопировать класс в свой проект, но я бы не рекомендовал использовать это вне scope test. Кроме того, можно добавить зависимость:
com.github.seregamorph hamcrest-more-matchers 0.1 test
import static com.github.seregamorph.hamcrest.TestLambdaUtils.unreferenceLambdaMethod; . ThrowingFunction fun = String::toLowerCase; Method method = unreferenceLambdaMethod(fun); assertEquals("toLowerCase", method.getName());
Get the declared method by name and parameter type in Java
The declared method can be obtained by name and parameter type by using the java.lang.Class.getDeclaredMethod() method. This method takes two parameters i.e. the name of the method and the parameter array of the method.
The getDeclaredMethod() method returns a Method object for the method of the class that matches the name of the method and the parameter array that are the parameters.
A program that gets the declared method by name and parameter type using the getDeclaredMethod() method is given as follows −
Example
package Test; import java.lang.reflect.*; public class Demo < public String str; private Integer func1() < return 1; >public void func2(String str) < this.str = "Stars"; >public static void main(String[] args) < Demo obj = new Demo(); Class c = obj.getClass(); try < Method m1 = c.getDeclaredMethod("func1", null); System.out.println("The method is: " + m1.toString()); Class[] argument = new Class[1]; argument[0] = String.class; Method m2 = c.getDeclaredMethod("func2", argument); System.out.println("The method is: " + m2.toString()); >catch(NoSuchMethodException e) < System.out.println(e.toString()); >> >
Output
The method is: private java.lang.Integer Test.Demo.func1() The method is: public void Test.Demo.func2(java.lang.String)
Now let us understand the above program.
In the class Demo, the two methods are func1() and func2(). A code snippet which demonstrates this is as follows −
public String str; private Integer func1() < return 1; >public void func2(String str)
In the method main(), an object obj is created of class Demo. Then getClass() is used to get the class of obj in c. Finally, the getDeclaredMethod() method is used to get the methods func1() and func2() and they are displayed. A code snippet which demonstrates this is as follows −
Demo obj = new Demo(); Class c = obj.getClass(); try
Invoke a method using reflection java code example
We would use invoke method for this If there are many overloaded methods with the same name, the compiler would invoke the method matching the parameter Invoke function Would be used to invoke the method using the Method object Syntax : methodObj: Object of method returned from the getDeclaredMethod Parameters: parameter values used to invoke the method. There are two functions used for this purpose: Invoking method with its name Finding a method by Name in a class and invoking the same 1.
How to Invoke Method by Name in Java Dynamically Using Reflection?
Java Reflection API provides us information about a Class to which the Object belongs to including the methods in this class. Using these Reflection API we would be able to get invoking pointer for a method in a class with its name.
There are two functions used for this purpose:
- Invoking method with its name
- Finding a method by Name in a class and invoking the same
1. Invoking method with its name
getDeclaredMethod() is used for this purpose
Class.getDeclaredMethod(“method name”, parameterType)
Method name: the method we want to find by name
Parameter Type : Type of parameters the method accepts
Return Type: This method would return an object with reference to the method’s address which would then be used to invoke the method. We would use invoke method for this
If there are many overloaded methods with the same name, the compiler would invoke the method matching the parameter
Invoke function
Would be used to invoke the method using the Method object
Method.invoke(classObj, param1, param2…)
methodObj: Object of method returned from the getDeclaredMethod
Parameters: parameter values used to invoke the method. If the method does not have any parameters to be passed, then we would pass null here
Java
Invoke method by Name in Java using Reflection! you invoked me with the message:hello
2. Finding a method by Name in a class and invoking the same
In case we don’t know the exact method parameters, we could also get all the methods in the class and search through the method by its name and then get details of it
- We would use getDeclaredMethods() API for the same. This would return array of Method objects in the class
- We can use this to loop through the Method objects and find the method by its name using the getName().
- Then we would use the getGenericParameterTypes() to find the parameter it takes and getGenericReturnType() to find its return type
- Once we have the parameter and return type, we would use our invoke function mentioned above to invoke the method
Method[] methods = Class.getDeclaredMethods()
Java
Find method by Name in Java using Reflection! sum is:30
Exception thrown by invoke method
Invoke method would throw InvocationTargetException when the underlying method being invoked throws an exception. We would be able to retrieve the method’s exception by using getCause() method of InvocationTargetException
Java Reflection private method with parameters best, Try using the getDeclaredMethod (singular) method, which takes as a parameter the method name and a varargs argument for the classes of the parameter types. That should get you directly to the method so you don’t have to iterate through all methods yourself. Example: clazz.getDeclaredMethod …
How to invoke a method which throws an Exception using Java Reflection?
You can get the cause of it that would be the original exception.
InvocationTargetException.getCause();
InvocationTargetException is a checked exception that wraps an exception thrown by an invoked method or constructor.
In your catch block, you could check if exception is from the type you expect and handle it.
One simple approach would be:
try < . >catch (InvocationTargetException ite) < if (ite.getCause() instanceof SomeExceptionType) < . >else < . >>
try < myMethod.invoke(null, myParam); >catch (InvocationTargetException e) < try < throw e.getCause(); >catch (MyCustomException e) < . >catch (MyOtherException e) < . >>
If you are trying to add a catch clause in the method that executes myMethod.invoke(null, myParam) , then that’s obviously not the right way of doing it. In this case you’re invoking the method via reflection and this is not the place to be catching the exception, as the invoke method throws other exceptions. Once you invoke the method that throws the exception, if there is an exception, it will get thrown and wrapped in an InvocationTargetException , if I recall correctly.
Check the last part of this explanation concerning the InvocationTargetException .
Reflection — In Java, it is possible to take a line of code as, Basically I want to pass methodA to methodB except methodA isn’t a method, but a line of code. Below is a full example of passing a method to a method using reflection. public class Relation
Java Reflection to call methods with multiple parameters
If you take a look at the javadoc for Class you’ll see that getMethod(. ) and getMethods() return only the public member methods.
In your code, the methods aren’t public so they are not found. What you want to use instead is getDeclaredMethod(. ) .
Tested it right now, and it works.
Java — How to change method behaviour through, Anyway, reflection does not allow you to change code behaviour, it can only explore current code, invoke methods and constuctors, change fields values, that kind of things. If you want to actually change the behaviour of a method you would have to use a bytecode manipulation library such as ASM. But this will …