Finding most specific overloaded method using MethodHandle

前端 未结 5 418
旧时难觅i
旧时难觅i 2020-12-14 01:50

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          


        
相关标签:
5条回答
  • 2020-12-14 02:12

    You can use MethodFinder.findMethod() to achieve it.

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

    Since it is in java root library, it is following JLS 15.12.

    0 讨论(0)
  • 2020-12-14 02:15

    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.

    0 讨论(0)
  • 2020-12-14 02:22

    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");
    
        }
    }
    
    0 讨论(0)
  • 2020-12-14 02:25

    Given the constraints that: a) the type of the parameter is only known at runtime, and b) there is only one parameter, a simple solution can be just walking up the class hierarchy and scanning the implemented interfaces like in the following example.

    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());
        }
    
    }
    
    0 讨论(0)
  • 2020-12-14 02:27

    No, I haven't seen anything like that in MethodHandle API. Similar thing exists in commons-beanutils as MethodUtils#getMatchingAccessibleMethod so you don't have to implement that.

    It will look something like this:

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

    You can convert to MethodHandle API or just use the Method directly:

    MethodHandle handle = MethodHandles.lookup().unreflect(method);
    handle.invoke(this, object);
    
    0 讨论(0)
提交回复
热议问题