25

特定のタイプ (クラス/インターフェース) 内に 3 つのメソッドがあるとします。

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

またはリフレクションを使用しMethodHandleて、型が実行時にのみ知られているオブジェクトの最も具体的なオーバーロードされたメソッドを見つけたいと思います。つまり、実行時にJLS 15.12を実行したいと考えています。

たとえば、上記の 3 つのメソッドを含む上記のタイプのメソッドに次のものがあるとします。

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 に従ってコンパイラが行うのと同じ種類の解決を実行時に自動的に実行するものがありますか?

4

5 に答える 5

10

基本的に、一連のパラメーターで実行できるすべてのメソッドを検索しました。そこで、parameterType から methodParameterType までの距離で並べ替えました。これにより、最も具体的なオーバーロードされたメソッドを取得できました。

テストする:

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

    Foo foo = new Foo();

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

フー:

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

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

もちろん、いくつかのリファクタリングとコメントで改善できます。

于 2016-10-20T04:52:58.667 に答える
8

s でこれを行う方法を見つけることができませんでしたが、 Javadocsに従って JLS の最も具体的な方法を見つけることを実装MethodHandleする興味深いものがあります。java.beans.Statement

executeメソッドは、プロパティと同じ名前のメソッドを見つけmethodName、ターゲットでメソッドを呼び出します。ターゲットのクラスが指定された名前で多くのメソッドを定義する場合、実装は Java 言語仕様 (15.11) で指定されたアルゴリズムを使用して最も具体的なメソッドを選択する必要があります。

それ自体を取得するにはMethod、リフレクションを使用して行うことができます。これが実際の例です:

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

    }
}
于 2016-10-20T21:02:47.893 に答える
3

いいえ、MethodHandle API でそのようなものは見たことがありません。同様のものがcommons-beanutilsasに存在するMethodUtils#getMatchingAccessibleMethodため、実装する必要はありません。

次のようになります。

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

MethodHandle API に変換するか、Method直接使用することができます。

MethodHandle handle = MethodHandles.lookup().unreflect(method);
handle.invoke(this, object);
于 2016-10-20T18:51:48.237 に答える