How to get parameter types using reflection?

后端 未结 1 1919
时光取名叫无心
时光取名叫无心 2020-12-16 03:43

I want to use functions having different numbers of parameters. The problem is that I don\'t know the number of parameters of each function, and also I don\'t know names of

相关标签:
1条回答
  • 2020-12-16 04:21

    What I usually do when I have to look up methods is to generate a cache key from the query I am doing and save the search result with this cache key in a map.

    Example:

    I know the method parameters are Boolean.TRUE, Arrays.asList("foo","bar","baz") and BigInteger.valueOf(77777l)

    My class contains a method with the signature

    public foo(boolean, Collection, Number)
    

    There's no way I can directly map the parameters to the parameter types because I just don't know which of the super classes or interfaces is the parameter type as you can see from the following table:

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

    Each of these pairs is compatible, but there's no way to find the compatible method without defining a comparison method, something like this:

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

    So what I'd do is have a method cache:

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

    and add a lookup method like this:

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

    That way, subsequent lookups will take much less time than the first one (for parameters of the same type).

    Of course since Class.getDeclaredMethods() uses a cache internally, the question is whether my cache improves performance at all. It's basically a question of what's faster:

    1. generating a cache key and querying a HashMap or
    2. iterating over all methods and querying for parameter compatibility

    My guess: for large classes (many methods), the first method will win, otherwise the second will

    0 讨论(0)
提交回复
热议问题