What is the easiest way to test (using reflection), whether given method (i.e. java.lang.Method instance) has a return type, which can be safely casted to List<String>?
Consider this snippet:
public static class StringList extends ArrayList<String> {}
public List<String> method1();
public ArrayList<String> method2();
public StringList method3();
All methods 1, 2, 3 fulfill the requirement. It's quite easy to test it for the method1 (via getGenericReturnType(), which returns instance of ParameterizedType), but for methods2 and 3, it's not so obvious. I imagine, that by traversing all getGenericSuperclass() and getGenericInterfaces(), we can get quite close, but I don't see, how to match the TypeVariable in List<E> (which occurs somewhere in the superclass interfaces) with the actual type parameter (i.e. where this E is matched to String).
Or maybe is there a completely different (easier) way, which I overlook?
EDIT: For those looking into it, here is method4, which also fulfills the requirement and which shows some more cases, which have to be investigated:
public interface Parametrized<T extends StringList> {
T method4();
}
I tried this code and it returns the actual generic type class so it seems the type info can be retrieved. However this only works for method 1 and 2. Method 3 does not seem to return a list typed String as the poster assumes and therefore fails.
public class Main {
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
try{
Method m = Main.class.getDeclaredMethod("method1", new Class[]{});
instanceOf(m, List.class, String.class);
m = Main.class.getDeclaredMethod("method2", new Class[]{});
instanceOf(m, List.class, String.class);
m = Main.class.getDeclaredMethod("method3", new Class[]{});
instanceOf(m, List.class, String.class);
m = Main.class.getDeclaredMethod("method4", new Class[]{});
instanceOf(m, StringList.class);
}catch(Exception e){
System.err.println(e.toString());
}
}
public static boolean instanceOf (
Method m,
Class<?> returnedBaseClass,
Class<?> ... genericParameters) {
System.out.println("Testing method: " + m.getDeclaringClass().getName()+"."+ m.getName());
boolean instanceOf = false;
instanceOf = returnedBaseClass.isAssignableFrom(m.getReturnType());
System.out.println("\tReturn type test succesfull: " + instanceOf + " (expected '"+returnedBaseClass.getName()+"' found '"+m.getReturnType().getName()+"')");
System.out.print("\tNumber of generic parameters matches: ");
Type t = m.getGenericReturnType();
if(t instanceof ParameterizedType){
ParameterizedType pt = (ParameterizedType)t;
Type[] actualGenericParameters = pt.getActualTypeArguments();
instanceOf = instanceOf
&& actualGenericParameters.length == genericParameters.length;
System.out.println("" + instanceOf + " (expected "+ genericParameters.length +", found " + actualGenericParameters.length+")");
for (int i = 0; instanceOf && i < genericParameters.length; i++) {
if (actualGenericParameters[i] instanceof Class) {
instanceOf = instanceOf
&& genericParameters[i].isAssignableFrom(
(Class) actualGenericParameters[i]);
System.out.println("\tGeneric parameter no. " + (i+1) + " matches: " + instanceOf + " (expected '"+genericParameters[i].getName()+"' found '"+((Class) actualGenericParameters[i]).getName()+"')");
} else {
instanceOf = false;
System.out.println("\tFailure generic parameter is not a class");
}
}
} else {
System.out.println("" + true + " 0 parameters");
}
return instanceOf;
}
public List<String> method1() {
return null;
}
public ArrayList<String> method2() {
return new ArrayList<String>();
}
public StringList method3() {
return null;
}
public <T extends StringList> T method4() {
return null;
}
This outputs:
Testing method: javaapplication2.Main.method1
Return type test succesfull: true (expected 'java.util.List' found 'java.util.List')
Number of generic parameters matches: true (expected 1, found 1)
Generic parameter no. 1 matches: true (expected 'java.lang.String' found 'java.lang.String')
Testing method: javaapplication2.Main.method2
Return type test succesfull: true (expected 'java.util.List' found 'java.util.ArrayList')
Number of generic parameters matches: true (expected 1, found 1)
Generic parameter no. 1 matches: true (expected 'java.lang.String' found 'java.lang.String')
Testing method: javaapplication2.Main.method3
Return type test succesfull: false (expected 'java.util.List' found 'com.sun.org.apache.xerces.internal.xs.StringList')
Number of generic parameters matches: true 0 parameters
Testing method: javaapplication2.Main.method4
Return type test succesfull: true (expected 'com.sun.org.apache.xerces.internal.xs.StringList' found 'com.sun.org.apache.xerces.internal.xs.StringList')
Number of generic parameters matches: true 0 parameters
Solving this in general is really not easy to do yourself using only the tools provided by Java itself. There are a lot of special cases (nested classes, type parameter bounds,...) to take care of.
That's why I wrote a library to make generic type reflection easier: gentyref. I added sample code (in the form of a JUnit test) to show how to use it to solve this problem: StackoverflowQ182872Test.java. Basically, you just call GenericTypeReflector.isSuperType using a TypeToken (idea from Neil Gafter) to see if List<String> is a supertype of the return type.
I also added a 5th test case, to show that an extra transformation on the return type (GenericTypeReflector.getExactReturnType) to replace type parameters with their values is sometimes needed.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With