aliteralmind
aliteralmind

Reputation: 20163

How to test if a private static function exists in a class, without having to catch a NoSuchMethodException?

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

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

Upvotes: 0

Views: 384

Answers (2)

Erwin Bolwidt
Erwin Bolwidt

Reputation: 31269

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

Upvotes: 2

Radiodef
Radiodef

Reputation: 37835

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.

Upvotes: 3

Related Questions