Reputation: 20163
I'm creating a utility function that returns a Method
object given all its pieces. Its parameters:
Class<?>
)Class<?>[]
)String
)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
Upvotes: 0
Views: 384
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
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