使用MethodHandle查找最特定的重载方法 - java

假设我在给定类型(类/接口)中有三种方法:

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

我想使用MethodHandle或反射,为类型仅在运行时才知道的对象找到最特定的重载方法。即我想在运行时执行JLS 15.12。

例如,假设我在上述类型的方法中具有以下内容,其中包含以下三种方法:

Object object = getLong(); // runtime type is Long *just an example*

MethodHandles.lookup()
             .bind(this, "foo", methodType(Void.class, object.getClass()))
             .invoke(object);

然后,从概念上讲,我希望选择foo(Number number),但是上面的方法将引发异常,因为API仅会查找foo(Long)方法,而不会寻找其他方法。请注意,此处Long的用法仅作为示例。实际上,对象的类型可以是任何东西。字符串,MyBar,整数,...等,等等。

MethodHandle API中是否有某些东西可以自动且在运行时执行与编译器遵循JLS 15.12相同的分辨率?

参考方案

基本上,我搜索了可以用一组参数执行的所有方法。因此,我按照parameterType与methodParameterType之间的距离对它们进行了排序。这样做,我可以获得最具体的重载方法。

去测试:

@Test
public void test() throws Throwable {
    Object object = 1;

    Foo foo = new Foo();

    MethodExecutor.execute(foo, "foo", Void.class, object);
}

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

方法执行器:

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

当然,可以通过一些重构和注释来改进它。

无法从ArrayList <String>转换为List <Comparable> - java

当我写下面的代码时,编译器说 无法从ArrayList<String>转换为List<Comparable>private List<Comparable> get(){ return new ArrayList<String>(); } 但是当我用通配符编写返回类型时,代码会编译。private List&l…

合并List <T>和List <Optional <T >> - java

鉴于: List<Integer> integers = new ArrayList<>(Arrays.asList( 10, 12 )); List<Optional<Integer>> optionalIntegers = Arrays.asList( Optional.of(5), Optional.em…

实例化类型<?>的泛型类 - java

我正在为SCJP / OCPJP学习,并且遇到了一个对我来说很奇怪的示例问题。该示例代码实例化了两个通用集合:List<?> list = new ArrayList<?>(); List<? extends Object> list2 = new ArrayList<? extends Object>(); …

List <Dog>是List <Animal>的子类吗?为什么Java泛型不是隐式多态的? - java

我对Java泛型如何处理继承/多态感到困惑。假设以下层次结构-动物(父母)狗-猫(儿童)因此,假设我有一个方法doSomething(List<Animal> animals)。根据继承和多态性的所有规则,我假设List<Dog>是List<Animal>,而List<Cat>是List<Animal&g…

如何使用Java流在第一个和最后一个嵌套对象之间创建map <k,v>? - java

假设我有以下课程:class A { int id; List<B> b; } class B { int id; } 我想在A.id到B.id的列表之间创建一个映射(Map<Integer, List<Integer>>,其中key = A.id,而List<Integer>对应于每个A的B.id字段的列表)…