views:

2552

answers:

4

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();
}
+6  A: 

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
Jasper
That works in very simple cases, but there's a lot wrong with this solution; it makes many assumptions.Just one simple example of what goes wrong:Take: interface I<T> extends List<String> {}It thinks for I<Integer> that the Integer is also the type parameter for List, instead of String.
Wouter Coekaerts
You confused the StringList defined in the description with a StringList from xerces.
Wouter Coekaerts
A: 

This thread on the java.net forums might be helpful (although I have to admit I didn't understand everything they said).

Michael Myers
A: 

I think you are already on the right track. Just keep using getGenericSuperclass() and getGenericInterface() until you start getting parameterized types back...

So basically:

//For string list
ParameterizedType type = (ParameterizedType)StringList.class.getGenericSuperclass();
System.out.println( type.getActualTypeArguments()[0] );

//for a descendant of string list
Class clazz = (Class)StringListChild.class.getGenericSuperclass();
ParameterizedType type = (ParameterizedType)clazz.getGenericSuperclass();
System.out.println( type.getActualTypeArguments()[0] );

You'd want to build something that was recursive that would check for this--maybe even go up the chain looking for java.util.List.

James Schek
+2  A: 

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.

Wouter Coekaerts