java dynamic reflection methodhandle

java - Encontrar el método sobrecargado más específico usando MethodHandle



dynamic reflection (5)

Supongamos que tengo tres métodos dentro de un tipo determinado (clase / interfaz):

public void foo(Integer integer); public void foo(Number number); public void foo(Object object);

Usando un MethodHandle o reflexión, me gustaría encontrar el método sobrecargado más específico para un objeto cuyo tipo solo se conoce en tiempo de ejecución. es decir, me gustaría hacer JLS 15.12 en tiempo de ejecución.

Por ejemplo, supongamos que tengo lo siguiente en un método del tipo mencionado anteriormente que contiene esos tres métodos:

Object object = getLong(); // runtime type is Long *just an example* MethodHandles.lookup() .bind(this, "foo", methodType(Void.class, object.getClass())) .invoke(object);

Entonces, conceptualmente, querría que se elija foo(Number number) , pero lo anterior arrojará una excepción ya que la API solo buscará un método foo(Long) y nada más. Tenga en cuenta que el uso de Long aquí es solo un ejemplo. El tipo de objeto podría ser cualquier cosa en la práctica; String, MyBar, Integer, ..., etc., etc.

¿Hay algo en la API de MethodHandle que automáticamente y en tiempo de ejecución tenga el mismo tipo de resolución que el compilador después de JLS 15.12?


Básicamente busqué todos los métodos que se pueden ejecutar con un conjunto de parámetros. Entonces, los ordené por la distancia entre parameterType y methodParameterType. Al hacer esto, podría obtener el método sobrecargado más específico.

Probar:

@Test public void test() throws Throwable { Object object = 1; Foo foo = new Foo(); MethodExecutor.execute(foo, "foo", Void.class, object); }

El Foo:

class Foo { public void foo(Integer integer) { System.out.println("integer"); } public void foo(Number number) { System.out.println("number"); } public void foo(Object object) { System.out.println("object"); } }

El MethodExecutor:

public class MethodExecutor{ private static final Map<Class<?>, Class<?>> equivalentTypeMap = new HashMap<>(18); static{ equivalentTypeMap.put(boolean.class, Boolean.class); equivalentTypeMap.put(byte.class, Byte.class); equivalentTypeMap.put(char.class, Character.class); equivalentTypeMap.put(float.class, Float.class); equivalentTypeMap.put(int.class, Integer.class); equivalentTypeMap.put(long.class, Long.class); equivalentTypeMap.put(short.class, Short.class); equivalentTypeMap.put(double.class, Double.class); equivalentTypeMap.put(void.class, Void.class); equivalentTypeMap.put(Boolean.class, boolean.class); equivalentTypeMap.put(Byte.class, byte.class); equivalentTypeMap.put(Character.class, char.class); equivalentTypeMap.put(Float.class, float.class); equivalentTypeMap.put(Integer.class, int.class); equivalentTypeMap.put(Long.class, long.class); equivalentTypeMap.put(Short.class, short.class); equivalentTypeMap.put(Double.class, double.class); equivalentTypeMap.put(Void.class, void.class); } public static <T> T execute(Object instance, String methodName, Class<T> returnType, Object ...parameters) throws InvocationTargetException, IllegalAccessException { List<Method> compatiblesMethods = getCompatiblesMethods(instance, methodName, returnType, parameters); Method mostSpecificOverloaded = getMostSpecificOverLoaded(compatiblesMethods, parameters); //noinspection unchecked return (T) mostSpecificOverloaded.invoke(instance, parameters); } private static List<Method> getCompatiblesMethods(Object instance, String methodName, Class<?> returnType, Object[] parameters) { Class<?> clazz = instance.getClass(); Method[] methods = clazz.getMethods(); List<Method> compatiblesMethods = new ArrayList<>(); outerFor: for(Method method : methods){ if(!method.getName().equals(methodName)){ continue; } Class<?> methodReturnType = method.getReturnType(); if(!canBeCast(returnType, methodReturnType)){ continue; } Class<?>[] methodParametersType = method.getParameterTypes(); if(methodParametersType.length != parameters.length){ continue; } for(int i = 0; i < methodParametersType.length; i++){ if(!canBeCast(parameters[i].getClass(), methodParametersType[i])){ continue outerFor; } } compatiblesMethods.add(method); } if(compatiblesMethods.size() == 0){ throw new IllegalArgumentException("Cannot find method."); } return compatiblesMethods; } private static Method getMostSpecificOverLoaded(List<Method> compatiblesMethods, Object[] parameters) { Method mostSpecificOverloaded = compatiblesMethods.get(0); int lastMethodScore = calculateMethodScore(mostSpecificOverloaded, parameters); for(int i = 1; i < compatiblesMethods.size(); i++){ Method method = compatiblesMethods.get(i); int currentMethodScore = calculateMethodScore(method, parameters); if(lastMethodScore > currentMethodScore){ mostSpecificOverloaded = method; lastMethodScore = currentMethodScore; } } return mostSpecificOverloaded; } private static int calculateMethodScore(Method method, Object... parameters){ int score = 0; Class<?>[] methodParametersType = method.getParameterTypes(); for(int i = 0; i < parameters.length; i++){ Class<?> methodParameterType = methodParametersType[i]; if(methodParameterType.isPrimitive()){ methodParameterType = getEquivalentType(methodParameterType); } Class<?> parameterType = parameters[i].getClass(); score += distanceBetweenClasses(parameterType, methodParameterType); } return score; } private static int distanceBetweenClasses(Class<?> clazz, Class<?> superClazz){ return distanceFromObjectClass(clazz) - distanceFromObjectClass(superClazz); } private static int distanceFromObjectClass(Class<?> clazz){ int distance = 0; while(!clazz.equals(Object.class)){ distance++; clazz = clazz.getSuperclass(); } return distance; } private static boolean canBeCast(Class<?> fromClass, Class<?> toClass) { if (canBeRawCast(fromClass, toClass)) { return true; } Class<?> equivalentFromClass = getEquivalentType(fromClass); return equivalentFromClass != null && canBeRawCast(equivalentFromClass, toClass); } private static boolean canBeRawCast(Class<?> fromClass, Class<?> toClass) { return fromClass.equals(toClass) || !toClass.isPrimitive() && toClass.isAssignableFrom(fromClass); } private static Class<?> getEquivalentType(Class<?> type){ return equivalentTypeMap.get(type); } }

Por supuesto, se puede mejorar con algunas refactorizaciones y comentarios.


Dadas las limitaciones que: a) el tipo del parámetro solo se conoce en el tiempo de ejecución, yb) solo hay un parámetro, una solución simple puede ser simplemente subir la jerarquía de clases y escanear las interfaces implementadas como en el siguiente ejemplo.

public class FindBestMethodMatch { public Method bestMatch(Object obj) throws SecurityException, NoSuchMethodException { Class<?> superClss = obj.getClass(); // First look for an exact match or a match in a superclass while(!superClss.getName().equals("java.lang.Object")) { try { return getClass().getMethod("foo", superClss); } catch (NoSuchMethodException e) { superClss = superClss.getSuperclass(); } } // Next look for a match in an implemented interface for (Class<?> intrface : obj.getClass().getInterfaces()) { try { return getClass().getMethod("foo", intrface); } catch (NoSuchMethodException e) { } } // Last pick the method receiving Object as parameter if exists try { return getClass().getMethod("foo", Object.class); } catch (NoSuchMethodException e) { } throw new NoSuchMethodException("Method not found"); } // Candidate methods public void foo(Map<String,String> map) { System.out.println("executed Map"); } public void foo(Integer integer) { System.out.println("executed Integer"); } public void foo(BigDecimal number) { System.out.println("executed BigDecimal"); } public void foo(Number number) { System.out.println("executed Number"); } public void foo(Object object) { System.out.println("executed Object"); } // Test if it works public static void main(String[] args) throws SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException { FindBestMethodMatch t = new FindBestMethodMatch(); Object param = new Long(0); Method m = t.bestMatch(param); System.out.println("matched " + m.getParameterTypes()[0].getName()); m.invoke(t, param); param = new HashMap<String,String>(); m = t.bestMatch(param); m.invoke(t, param); System.out.println("matched " + m.getParameterTypes()[0].getName()); } }


No pude encontrar una manera de hacer esto con MethodHandle s, pero hay un java.beans.Statement interesante que implementa la búsqueda del método más específico de JLS según los Javadocs :

El método de execute encuentra un método cuyo nombre es el mismo que la propiedad methodName e invoca el método en el destino. Cuando la clase del objetivo define muchos métodos con el nombre dado, la implementación debe elegir el método más específico utilizando el algoritmo especificado en la Especificación del lenguaje Java (15.11).

Para recuperar el Method sí, podemos hacerlo utilizando la reflexión. Aquí hay un ejemplo de trabajo:

import java.beans.Statement; import java.lang.reflect.Method; public class ExecuteMostSpecificExample { public static void main(String[] args) throws Exception { ExecuteMostSpecificExample e = new ExecuteMostSpecificExample(); e.process(); } public void process() throws Exception { Object object = getLong(); Statement s = new Statement(this, "foo", new Object[] { object }); Method findMethod = s.getClass().getDeclaredMethod("getMethod", Class.class, String.class, Class[].class); findMethod.setAccessible(true); Method mostSpecificMethod = (Method) findMethod.invoke(null, this.getClass(), "foo", new Class[] { object.getClass() }); mostSpecificMethod.invoke(this, object); } private Object getLong() { return new Long(3L); } public void foo(Integer integer) { System.out.println("Integer"); } public void foo(Number number) { System.out.println("Number"); } public void foo(Object object) { System.out.println("Object"); } }


No, no he visto nada parecido en MethodHandle API. Algo similar existe en commons-beanutils como MethodUtils#getMatchingAccessibleMethod por lo que no tiene que implementar eso.

Se verá algo como esto:

Object object = getLong(); Method method = MethodUtils.getMatchingAccessibleMethod(this.getClass(), "foo", object.getClass());

Puede convertir a MethodHandle API o simplemente usar el Method directamente:

MethodHandle handle = MethodHandles.lookup().unreflect(method); handle.invoke(this, object);


Puede usar MethodFinder.findMethod() para lograrlo.

@Test public void test() throws Exception { Foo foo = new Foo(); Object object = 3L; Method method = MethodFinder.findMethod(Foo.class, "foo", object.getClass()); method.invoke(foo, object); } public static class Foo { public void foo(Integer integer) { System.out.println("integer"); } public void foo(Number number) { System.out.println("number"); } public void foo(Object object) { System.out.println("object"); } }

Como está en la biblioteca raíz java, sigue a JLS 15.12.