【问题标题】:How to test whether method return type matches List<String>如何测试方法返回类型是否匹配 List<String>
【发布时间】:2008-10-08 13:53:20
【问题描述】:

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

考虑一下这个sn-p:

public static class StringList extends ArrayList<String> {}

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

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

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

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

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

【问题讨论】:

  • 哇,我很惊讶这如此复杂。我刚刚测试并了解到 (obj instanceof List) 无法编译! wtf?

标签: java generics reflection


【解决方案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;
}

这个输出:

测试方法:javaapplication2.Main.method1 返回类型测试成功:true(预期'java.util.List'找到'java.util.List') 泛型参数匹配数:true(预期 1,找到 1) 通用参数编号1 个匹配项:真(预期 'java.lang.String' 发现 'java.lang.String') 测试方法:javaapplication2.Main.method2 返回类型测试成功:true(预期'java.util.List'找到'java.util.ArrayList') 泛型参数匹配数:true(预期 1,找到 1) 通用参数编号1 个匹配项:真(预期 'java.lang.String' 发现 'java.lang.String') 测试方法:javaapplication2.Main.method3 返回类型测试成功:false(预期 'java.util.List' 发现 'com.sun.org.apache.xerces.internal.xs.StringList') 泛型参数匹配数:true 0 个参数 测试方法:javaapplication2.Main.method4 返回类型测试成功:true(预期 'com.sun.org.apache.xerces.internal.xs.StringList' 发现 'com.sun.org.apache.xerces.internal.xs.StringList') 泛型参数匹配数:true 0 个参数

【讨论】:

  • 这适用于非常简单的情况,但这个解决方案有很多问题;它做了很多假设。只是一个简单的错误示例:Take: interface I extends List {} 它认为对于 I Integer 也是 List 的类型参数,而不是 String。
  • 您将描述中定义的 StringList 与来自 xerces 的 StringList 混淆了。
【解决方案2】:

一般来说,仅使用 Java 本身提供的工具来解决这个问题并不容易。有很多特殊情况(嵌套类、类型参数边界……)需要处理。 这就是为什么我编写了一个库来简化泛型类型反射:gentyref。我添加了示例代码(以 JUnit 测试的形式)来展示如何使用它来解决这个问题:StackoverflowQ182872Test.java。基本上,您只需使用TypeToken(来自Neil Gafter 的想法)调用GenericTypeReflector.isSuperType 来查看List&lt;String&gt; 是否是返回类型的超类型。

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

【讨论】:

    【解决方案3】:

    java.net 论坛上的This thread 可能会有所帮助(尽管我不得不承认我没有理解他们所说的一切)。

    【讨论】:

      【解决方案4】:

      我认为你已经在正确的轨道上。继续使用 getGenericSuperclass() 和 getGenericInterface() 直到您开始获取参数化类型...

      所以基本上:

      //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] );
      

      您可能想要构建一个可以检查此问题的递归程序——甚至可能会顺着链条向上寻找 java.util.List。

      【讨论】:

        猜你喜欢
        • 2010-12-20
        • 2011-01-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-09-30
        • 1970-01-01
        • 2017-01-06
        • 2014-05-29
        相关资源
        最近更新 更多