测试(使用反射)最简单的方法是什么,给定的方法(即java.lang.method实例)是否有返回类型,可以安全地转换为List<字符串>?
考虑一下这个片段:
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()和getGenericInterface(),我们可以非常接近,但我不知道如何匹配List<E>(发生在超类接口中的某个地方)与实际类型参数(即该e与String匹配的地方)。
或者有没有一种完全不同的(更容易的)方式,我忽略了?
编辑:对于那些研究它的人来说,这里有方法4,它也满足了要求,并显示了一些需要调查的更多情况:
public interface Parametrized<T extends StringList> {
T method4();
}
我尝试了这段代码,它返回了实际的泛型类型类,因此似乎可以检索类型信息。然而,这仅适用于方法1和2。方法3似乎没有像海报所假设的那样返回列表类型的String,因此失败了。
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个泛型参数匹配:true(应为"java.lang.String",而找到的是"java.lang.String")测试方法:javaapplication2.Main.method2返回类型测试成功l:true(应为"java.util.List",而找到"java.util.ArrayList")匹配的泛型参数数:true(应为1,找到1)第1个泛型参数匹配:true(应为"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返回类型测试成功l:true(应为"com.sun.org.apache.xerces.internal.xs.StringList',而找到的是"com.sun.org/apache.xerces.intranet.xs.StringList'")匹配的泛型参数数:true 0个参数
仅使用Java本身提供的工具解决这一问题通常并不容易。有很多特殊情况(嵌套类、类型参数边界等)需要处理。这就是为什么我写了一个库来简化泛型类型反射:gentyref。我添加了示例代码(以JUnit测试的形式)来展示如何使用它来解决这个问题:StackoverflowQ182872Test.java。基本上,你只需要使用TypeToken
(Neil Gafter的想法)来调用GenericTypeReflector.isSuperType
,看看List<String>
是否是返回类型的超类型。
我还添加了第五个测试用例,以表明有时需要对返回类型(GenericTypeReflector.getExactReturnType
)进行额外的转换,将类型参数替换为其值。
java.net论坛上的这个帖子可能会有所帮助(尽管我不得不承认我并没有理解他们所说的一切)。
我认为你已经走上了正轨。只需继续使用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.