如何测试方法返回类型是否与 List<String 匹配>

2022-09-03 07:27:42

测试(使用反射)的最简单方法是什么,给定的方法(即.java.lang.Method实例)是否具有返回类型,可以安全地转换为List<String>?

请考虑以下代码段:

public static class StringList extends ArrayList<String> {}

public List<String> method1();
public ArrayList<String> method2();
public StringList method3();

所有方法 1、2、3 均满足要求。对于 method1(通过 getGenericReturnType())测试它很容易,它返回 ParameterizedType 的实例),但对于 methods2 和 3,它并不那么明显。我想,通过遍历所有getGenericSuperclass()和getGenericInterfaces(),我们可以非常接近,但我不明白如何将List<E>中的TypeVariable(发生在超类接口中的某个地方)与实际的类型参数(即这个E与String匹配的地方)相匹配。

或者也许有一种完全不同的(更简单的)方法,我忽略了?

编辑:对于那些研究它的人来说,这里是方法4,它也满足了要求,并显示了一些必须调查的更多案例:

public interface Parametrized<T extends StringList> {
    T method4();
}

答案 1

我尝试了此代码,它返回实际的泛型类型类,因此似乎可以检索类型信息。但是,这仅适用于方法 1 和 2。方法 3 似乎没有像海报假定的那样返回列表类型字符串,因此失败。

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;
}

此输出:

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

答案 2

一般来说,仅使用Java本身提供的工具自己解决这个问题真的不容易。有很多特殊情况(嵌套类,类型参数边界,...)需要处理。这就是为什么我写了一个库来使泛型类型反射更容易:gentyref。我添加了示例代码(以JUnit测试的形式)来演示如何使用它来解决这个问题:StackoverflowQ182872Test.java。基本上,你只需使用一个(来自Neil Gafter的想法)来调用,看看是否是返回类型的超类型。GenericTypeReflector.isSuperTypeTypeTokenList<String>

我还添加了第 5 个测试用例,以表明有时需要在返回类型 () 上进行额外的转换,以将类型参数替换为其值。GenericTypeReflector.getExactReturnType