0

I'm creating a utility function that returns a Method object given all its pieces. Its parameters:

  • return type (Class<?>)
  • containing class (Class<?>[])
  • function name (String)
  • parameter-class array (Class<?>[])

Notice the containing-class parameter is an array. Exactly one of those classes must contain the method, but you can't know which one. It's a way for default classes to be specified, in addition to the one that may be explicitly specified.

My goal is to search each class in this array, in order, and if it contains that method, return its Method object. I would like to do this without having to try-catch for NoSuchMethodException (as described in this question). Since the method may be private, using Class.getMethods() is not possible.

(It also allows for private static, but I'm not sure that if that affects this question.)

How do you search for a private method in this way? How do you make a "does function exist in class" test, without having to use a NoSuchMethodException as the "false" logic?

More information: https://www.google.com/search?q=class+contains+method+java

Community
  • 1
  • 1
aliteralmind
  • 19,847
  • 17
  • 77
  • 108
  • You don't want to catch a `NoSuchMethodError`. Do you also object to catching a `NoSuchMethodException`, which is not the same thing? Because the most straightforward answer would be to use `getDeclaredMethod` (not `getDeclaredMethods`) and catch the `NoSuchMethodException`. But I shan't bother posting it as an answer if you don't want to catch `NoSuchMethodException`. – Dawood ibn Kareem Apr 05 '14 at 03:56
  • 1
    @DavidWallace: My goal is to avoid catching exceptions as a replacement for logic. Perhaps you shall enlighten me as to why I am wrong. Or perhaps you shan't. – aliteralmind Apr 05 '14 at 13:28
  • And yes, I meant Exception, not Error. – aliteralmind Apr 05 '14 at 16:42
  • I find there are cases where one can't avoid catching exceptions for logic. The most common one is when you're trying to tell if a String represents a number, so you do something like `Long.parseLong(myString)` and catch the `NumberFormatException`. I consider this a weakness in the API - there doesn't seem to be a better way to do it. I would actually prefer it if `Long.parseLong` returned `Long` instead of `long` - and returned `null` instead of throwing the exception. But we work with what we're given. Similarly, I would prefer if `getDeclaredMethod` returned `null` when the method ... – Dawood ibn Kareem Apr 05 '14 at 19:03
  • ... doesn't exist. But I'm not above catching `NoSuchMethodException`, because we work with what the API gives us. Now `NoSuchMethodError` is another story, because that's what gets thrown if we actually try to run a method that doesn't exist, and I wouldn't dream of trying to catch this one. But I consider catching `NoSuchMethodException` as harmless and necessary. – Dawood ibn Kareem Apr 05 '14 at 19:05

2 Answers2

3

Use getDeclaredMethods which returns all methods, including the ones that aren't public.

for(Class<?> cls : classesToCheck) {
    for(Method m : cls.getDeclaredMethods()) {
        if(!m.getName().equals(methodName))
            continue;
        if(m.getReturnType() != returnType)
            continue;

        Class<?>[] pams = m.getParameterTypes();
        if(pams.length != pamsToCheckFor.length)
            continue;

        int i;
        for(i = 0; i < pams.length; i++) {
            if(pams[i] != pamsToCheckFor[i])
                break;
        }

        if(i == pams.length)
            return m; // or return true
    }
}

// not found

That is quite a bit of work but sure it can be done.

Note that if you are doing this type of thing with the same classes frequently you might want to store the Method objects locally somewhere, like a HashMap. Calling getFields/getMethods/getConstructors/etc creates a new array every time and makes copies of all the objects in it. It is a comparatively expensive operation just to find one thing.

As trivia, there is a class called sun.reflect.ReflectionFactory that makes these defensive copies.

The source code to java.lang.Class also reveals that the methods like getMethod and getDeclaredMethod do something strikingly similar to the above snippet except they potentially don't make copies. So as part of a performance optimization the copying is something to weigh. If the method not existing is considered an unlikely result, unless you are caching the Methods like I suggested, searching the array may not actually be faster.

Consider searching a class like java.lang.String. This is a pretty large class and calling getDeclaredMethods on it will create a pretty large amount of objects.

Radiodef
  • 37,180
  • 14
  • 90
  • 125
  • The classes I'll be using this with do not contain too many functions, but I will be doing it repeatedly on the same classes. I'll consider storing the method objects as as you suggest. – aliteralmind Apr 05 '14 at 17:07
2

I like @Radiodef's answer, but it is missing something: getDeclaredClass only returns the methods declared in the Class itself, not in its superclasses. Since superclass methods are typically also considered methods of the class, you need to search through them too.

Second, you when you specify a return type, you typically want to find a method that returns something that you can assign to that return type, not the exact return type. In Java, it is legal for a method that overrides a superclass method, to return a subclass of return type of the superclass. I.e. the superclass has Number myNumber(), then the subclass may have @Override Integer myNumber().

If you take that into account as well, you arrive at this code:

public static Method findMethod(Class<?> returnType, Collection<? extends Class<?>> containingClasses, String functionName, Class<?>[] parameters) {
    for (Class<?> containingClass : containingClasses) {
        Method m = findMethodInClass(returnType, containingClass, functionName, parameters);
        if (m != null)
            return m;
    }
    return null;
}

private static Method findMethodInClass(Class<?> returnType, Class<?> containingClass, String functionName, Class<?>[] parameters) {
    for (Method m : containingClass.getDeclaredMethods()) {
        if (checkMethod(m, returnType, functionName, parameters))
            return m;
    }
    if (containingClass.getSuperclass() != null)
        return findMethodInClass(returnType, containingClass.getSuperclass(), functionName, parameters);
    else
        return null;
}

private static boolean checkMethod(Method method, Class<?> returnType, String functionName, Class<?>[] parameters) {
    if (!method.getName().equals(functionName))
        return false;
    // Also allow overridden classes that return a subtype of the requested type
    if (!returnType.isAssignableFrom(method.getReturnType()))
        return false;

    Class<?>[] actualParameters = method.getParameterTypes();
    if (actualParameters.length != parameters.length)
        return false;

    for (int i = 0; i < actualParameters.length; i++) {
        if (actualParameters[i] != parameters[i])
            return false;
    }
    return true;
}

public static void main(String[] args) {
    System.out.println(findMethod(Integer.TYPE, Collections.singleton(Integer.class), "intValue", new Class<?>[0]));
    System.out.println(findMethod(String.class, Collections.singleton(Random.class), "toString", new Class<?>[0]));
}

I would advise against using any classes in the sun.* packages for any reason, though; including sun.reflect.ReflectionFactory. These classes are not part of the supported, public interface of Java and can be changed or removed at any time.

See: what happened to sun.* packages

Community
  • 1
  • 1
Erwin Bolwidt
  • 30,799
  • 15
  • 56
  • 79
  • Good points regarding the super-class and return types. I'm curious why you mention the sun classes, as I've not heard of them as an alternative to java.lang.reflect, until reading it in your answer. – aliteralmind Apr 05 '14 at 13:27
  • 1
    I was referring to @RadioDef's answer. Although I understand his concerns for performance, I don't belief that it warrant the use of an unpublished API. But his other comments about caching are very sensible if you are going to use this method a lot inside loops in your code. – Erwin Bolwidt Apr 05 '14 at 13:47
  • 1
    To be clear, I never suggested to actually use ReflectionFactory. – Radiodef Apr 05 '14 at 21:03