Что я обычно делаю, когда мне приходится искать методы, так это генерировать ключ кеша из запроса, который я делаю, и сохранять результат поиска с этим ключом кеша на карте.
Пример:
Я знаю, что параметры метода 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()
использует кэш для внутреннего использования, вопрос заключается в том, улучшает ли мой кэш производительность. Вопрос в том, что быстрее:
- генерация ключа кеша и запрос HashMap или
- перебирает все методы и запрашивает совместимость параметров
Мое предположение: для больших классов (много методов) первый метод победит, в противном случае второй будет