java - MethodHandle का उपयोग कर सबसे विशिष्ट ओवरलोडेड विधि ढूँढना




dynamic reflection (4)

मान लीजिए कि मेरे पास दिए गए प्रकार (कक्षा / इंटरफ़ेस) के अंदर तीन विधियां हैं:

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

MethodHandle या प्रतिबिंब का उपयोग करके, मैं उस ऑब्जेक्ट के लिए सबसे विशिष्ट ओवरलोडेड विधि ढूंढना चाहता हूं जिसके प्रकार को केवल रनटाइम पर जाना जाता है। यानी मैं रनटाइम पर जेएलएस 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) चाहता हूं, लेकिन उपर्युक्त अपवाद फेंक देगा क्योंकि एपीआई केवल एक foo(Long) विधि की तलाश करेगा और कुछ भी नहीं। ध्यान दें कि यहां Long उपयोग एक उदाहरण के रूप में है। वस्तु का प्रकार अभ्यास में कुछ भी हो सकता है; स्ट्रिंग, माईबार, इंटीजर, ..., आदि, आदि

क्या मेथडहैंडल एपीआई में कुछ है जो स्वचालित रूप से और रनटाइम पर एक ही तरह का संकल्प करता है जो संकलक जेएलएस 15.12 का पालन करता है?


असल में मैंने उन सभी विधियों की खोज की जिन्हें पैरामीटर के सेट के साथ निष्पादित किया जा सकता है। इसलिए, मैंने पैरामीटर के बीच की दूरी से विधि को पैरामीटर टाइप टाइप किया। ऐसा करने से, मुझे सबसे विशिष्ट ओवरलोडेड विधि मिल सकती है।

परीक्षा करना:

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

विधि निष्पादक:

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

कुछ रिफैक्टरिंग और टिप्पणियों के साथ इसका सुधार किया जा सकता है।


आप इसे प्राप्त करने के लिए MethodFinder.findMethod() का उपयोग कर सकते हैं।

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

चूंकि यह जावा रूट लाइब्रेरी में है, यह जेएलएस 15.12 का पालन कर रहा है।


बाधाओं को देखते हुए कि: ए) पैरामीटर का प्रकार केवल रनटाइम पर जाना जाता है, और बी) केवल एक पैरामीटर होता है, एक साधारण समाधान केवल वर्ग पदानुक्रम पर चल रहा है और निम्नलिखित उदाहरणों में लागू इंटरफेस स्कैन कर सकता है।

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

}

मुझे MethodHandle साथ ऐसा करने का कोई तरीका नहीं मिला, लेकिन एक दिलचस्प java.beans.Statement जो java.beans.Statement अनुसार जेएलएस की सबसे विशिष्ट विधि खोजने में लागू करता है:

execute विधि एक विधि पाती है जिसका नाम विधि नाम के समान है, और लक्ष्य पर विधि को आमंत्रित करता है। जब लक्ष्य की श्रेणी दिए गए नाम के साथ कई विधियों को परिभाषित करती है तो कार्यान्वयन जावा भाषा विशिष्टता (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");

    }
}




methodhandle