Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Finding most specific overloaded method using MethodHandle

Tags:

Suppose I have three methods inside a given type (class/interface):

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

Using a MethodHandle or reflection, I'd like to find the most specific overloaded method for an object of which the type is only known at runtime. i.e. I'd like to do JLS 15.12 at runtime.

For instance, suppose I have the following in a method of the type mentioned above that contains those three methods:

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

Then I conceptually would want foo(Number number) to be chosen, but the above will throw an exception since the API will only look for a foo(Long) method and nothing else. Note that the usage of Long here is just as an example. The type of the object could be anything in practice; String, MyBar, Integer, ..., etc., etc.

Is there something in the MethodHandle API that automatically and at runtime does the same kind of resolution that the compiler does following JLS 15.12?

like image 353
Arjan Tijms Avatar asked Oct 17 '16 11:10

Arjan Tijms


People also ask

How can you tell overloaded methods apart?

Overloaded methods are differentiated by the number and the type of the arguments passed into the method. In the code sample, draw(String s) and draw(int i) are distinct and unique methods because they require different argument types.

Can you overload two methods with the same number of parameters?

No, you cannot overload a method based on different return type but same argument type and number in java. same name. different parameters (different type or, different number or both).

Can you overload a method in a subclass?

Note: In a subclass, you can overload the methods inherited from the superclass. Such overloaded methods neither hide nor override the superclass instance methods—they are new methods, unique to the subclass.


2 Answers

Basically I searched all methods that can be executed with a set of parameters. So, I sorted them by the distance between the parameterType to the methodParameterType. Doing this, I could get the most specific overloaded method.

To test:

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

The 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");     } } 

The 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);     } } 

Ofcourse it can be improved with some refactoring and comments.

like image 65
Paulo Avatar answered Oct 12 '22 01:10

Paulo


I couldn't find a way to do this with MethodHandles, but there is an interesting java.beans.Statement that implements finding the JLS' most specific method according to the Javadocs:

The execute method finds a method whose name is the same as the methodName property, and invokes the method on the target. When the target's class defines many methods with the given name the implementation should choose the most specific method using the algorithm specified in the Java Language Specification (15.11).

To retrieve the Method itself, we can do so using reflection. Here's a working example:

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");      } } 
like image 33
M A Avatar answered Oct 12 '22 00:10

M A