Как получить типы параметров, используя отражение? - PullRequest
8 голосов
/ 11 февраля 2011

Я хочу использовать функции с разным количеством параметров.Проблема в том, что я не знаю количество параметров каждой функции, а также не знаю имен функций, поскольку они хранятся в массиве.Я знаю только имя класса, но не хочу использовать getDeclaredMethods, так как это увеличит время поиска.Есть ли способ получить типы параметров для каждой функции?

1 Ответ

4 голосов
/ 11 февраля 2011

Что я обычно делаю, когда мне приходится искать методы, так это генерировать ключ кеша из запроса, который я делаю, и сохранять результат поиска с этим ключом кеша на карте.

Пример:

Я знаю, что параметры метода Boolean.TRUE, Arrays.asList("foo","bar","baz") и BigInteger.valueOf(77777l)

Мой класс содержит метод с подписью

public foo(boolean, Collection, Number)

Нет способа напрямую сопоставить параметры с типами параметров, поскольку я просто не знаю, какой из суперклассов или интерфейсов является типом параметра, как вы можете видеть из следующей таблицы:

Expected Type          |  What I have
-----------------------------------------------------
 boolean               |  java.lang.Boolean
 java.util.Collection  |  java.util.Arrays$ArrayList
 java.lang.Number      |  java.math.BigInteger

Каждая из этих пар совместима, но нет способа найти совместимый метод без определения метода сравнения, что-то вроде этого:

// determine whether a method's parameter types are compatible
// with my arg array
public static boolean isCompatible(final Method method,
    final Object[] params) throws Exception{
    final Class<?>[] parameterTypes = method.getParameterTypes();
    if(params.length != parameterTypes.length){
        return false;
    }
    for(int i = 0; i < params.length; i++){
        final Object object = params[i];
        final Class<?> paramType = parameterTypes[i];
        if(!isCompatible(object, paramType)){
            return false;
        }
    }
    return true;
}

// determine whether a single object is compatible with
// a single parameter type
// careful: the object may be null
private static boolean isCompatible(final Object object,
    final Class<?> paramType) throws Exception{
    if(object == null){
        // primitive parameters are the only parameters
        // that can't handle a null object
        return !paramType.isPrimitive();
    }
    // handles same type, super types and implemented interfaces
    if(paramType.isInstance(object)){
        return true;
    }
    // special case: the arg may be the Object wrapper for the
    // primitive parameter type
    if(paramType.isPrimitive()){
        return isWrapperTypeOf(object.getClass(), paramType);
    }
    return false;

}

/*
  awful hack, can be made much more elegant using Guava:

  return Primitives.unwrap(candidate).equals(primitiveType);

*/
private static boolean isWrapperTypeOf(final Class<?> candidate,
    final Class<?> primitiveType) throws Exception{
    try{
        return !candidate.isPrimitive()
            && candidate
                .getDeclaredField("TYPE")
                .get(null)
                .equals(primitiveType);
    } catch(final NoSuchFieldException e){
        return false;
    } catch(final Exception e){
        throw e;
    }
}

Итак, я бы сделал кеш методов:

private static final Map<String, Set<Method>> methodCache;

и добавьте метод поиска следующим образом:

public static Set<Method> getMatchingMethods(final Class<?> clazz,
    final Object[] args) throws Exception{
    final String cacheKey = toCacheKey(clazz, args);
    Set<Method> methods = methodCache.get(cacheKey);
    if(methods == null){
        final Set<Method> tmpMethods = new HashSet<Method>();
        for(final Method candidate : clazz.getDeclaredMethods()){
            if(isCompatible(candidate, args)){
                tmpMethods.add(candidate);
            }
        }
        methods = Collections.unmodifiableSet(tmpMethods);
        methodCache.put(cacheKey, methods);
    }
    return methods;
}

private static String toCacheKey(final Class<?> clazz, final Object[] args){
    final StringBuilder sb = new StringBuilder(clazz.getName());
    for(final Object obj : args){
        sb.append('-').append(
            obj == null ? "null" : obj.getClass().getName());
    }
    return sb.toString();
}

Таким образом, последующий поиск займет намного меньше времени, чем первый (для параметров того же типа).

Конечно, поскольку Class.getDeclaredMethods() использует кэш для внутреннего использования, вопрос заключается в том, улучшает ли мой кэш производительность. Вопрос в том, что быстрее:

  1. генерация ключа кеша и запрос HashMap или
  2. перебирает все методы и запрашивает совместимость параметров

Мое предположение: для больших классов (много методов) первый метод победит, в противном случае второй будет

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...