2011-02-11 26 views
8

Quiero usar funciones con diferentes números de parámetros. El problema es que no sé la cantidad de parámetros de cada función, y tampoco sé los nombres de las funciones, ya que están almacenadas en una matriz. Solo conozco el nombre de la clase, pero no quiero usar getDeclaredMethods ya que aumentará el tiempo de búsqueda. ¿Hay alguna manera de obtener los tipos de parámetros para cada función?¿Cómo obtener los tipos de parámetros usando la reflexión?

+1

* como están almacenados en una matriz * ¿quién es 'ellos'? Los métodos o los parámetros? por favor, muestre un código para ilustrar que –

+0

Realmente no entiendo cómo usar "getDeclaredMethods" "aumentaría el tiempo de búsqueda" ... lo que quiere son los tipos de parámetros de todos los métodos ¿no? Entonces, tendrá que iterar sobre todos los métodos de todos modos IIUC ... – Kellindil

+0

getDeclaredMethods almacena en caché los resultados que obtiene por lo que no hay mucho golpe de rendimiento para llamar esto repetidamente en una clase. –

Respuesta

4

Lo que suelo hacer cuando tengo que buscar métodos es generar una clave de caché a partir de la consulta que estoy haciendo y guardar el resultado de búsqueda con esta clave de caché en un mapa.

Ejemplo:

Sé que los parámetros del método son Boolean.TRUE, Arrays.asList("foo","bar","baz") y BigInteger.valueOf(77777l)

Mi clase contiene un método con la firma

public foo(boolean, Collection, Number) 

No hay manera de que pueda asignar directamente los parámetros a los tipos de parámetros porque simplemente no sé cuál de las super clases o interfaces es el tipo de parámetro, como se puede ver en la siguiente tabla:

Cada uno de estos pares es compatible, pero no hay manera de encontrar el método compatibles sin definir un método de comparación, algo como esto:

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

Así que lo que haría es tener un caché método :

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

y añadir un método de búsqueda de la siguiente manera:

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

De esta manera, las búsquedas posteriores tomarán mucho menos tiempo que la primera (para parámetros del mismo tipo).

Por supuesto, desde Class.getDeclaredMethods() utiliza un caché internamente, la pregunta es si mi caché mejora el rendimiento en absoluto. Se trata básicamente de una cuestión de lo que es más rápido:

  1. generar una clave de caché y consulta de un HashMap o
  2. iterar sobre todos los métodos y consulta para la compatibilidad de parámetros

Mi suposición: para grandes clases (muchos métodos), el primer método ganará, de lo contrario el segundo

Cuestiones relacionadas