我使用反射来发现类及其超类的方法,以及方法参数和返回值的类型。这大部分都有效,但我遇到了一些特定的通用案例。假设我有一个基类:
package net.redpoint.scratch;
public class Base<E> {
public E getE() { return null; }
}
一个子类:
package net.redpoint.scratch;
public class Derived extends Base<String> {}
使用反射我可以遍历Derived的方法并获取arg和返回类型(代码省略,但它工作正常)。但是,我也想知道继承的方法。使用下面的代码,我可以非常非常接近。但是获得getE()的正确返回类型是我的选择。我可以得到泛型类型“E”但不是实际类型“java.lang.String”:
package net.redpoint.scratch;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
public class Scratch {
public static void main(String[] args) throws Exception {
Class clazz = Class.forName("net.redpoint.scratch.Derived");
Type superType = clazz.getGenericSuperclass();
if (superType instanceof ParameterizedType) {
ParameterizedType superPt = (ParameterizedType)superType;
Type[] typeArgs = superPt.getActualTypeArguments();
Type t0 = typeArgs[0];
// This is "java.lang.String"
System.out.println(t0.getTypeName());
Type superRawType = superPt.getRawType();
if (superRawType instanceof Class) {
Class superRawClazz = (Class)superRawType;
for (Method method : superRawClazz.getDeclaredMethods()) {
if (method.getName().equals("getE")) {
Type returnType = method.getGenericReturnType();
if (returnType instanceof TypeVariable) {
TypeVariable tv = (TypeVariable)returnType;
// This is "E"
System.out.println(tv.getName());
// How do I associate this "E" back to the correct type "java.lang.String"
}
}
}
}
}
}
}
输出结果为:
java.lang.String
E
我的问题是,如何找到“E”实际上是“java.lang.String”?我怀疑它与查找typeArgs []有关,但我不知道如何到达那里。请不要回应,除非你真的使用泛型反射。我看过很多帖子都有“类型擦除可以防止这种情况”的答案,但事实并非如此。
答案 0 :(得分:4)
我建议使用番石榴。例如:
Type returnType =
new TypeToken<Derived>() {}
.resolveType(Base.class.getMethod("getE").getGenericReturnType());
请参阅TypeToken.resolveType(Type)
。
替代方案非常复杂,但可能。您需要遍历层次结构并将类型变量映射到自己的类型参数。
处理最微不足道的案件将是这样的:
static Type getArgument(TypeVariable<?> var,
ParameterizedType actual) {
GenericDeclaration decl = var.getGenericDeclaration();
if (decl != actual.getRawType())
throw new IllegalArgumentException();
TypeVariable<?>[] vars = decl.getTypeParameters();
for (int i = 0; i < vars.length; ++i) {
if (var == vars[i]) {
return actual.getActualTypeArguments()[i];
}
}
return null;
}
如果你有类似的东西,那种简单的方法会失败:
abstract class AbstractDerived<T> extends Base<T> {}
class Derived extends AbstractDerived<String> {}
在这种情况下,您需要首先将E
从Base
映射到T
AbstractDerived
,然后将T
映射到String
,所以该方法必须递归或迭代超类型。我有一个类似于here的更复杂的例子,但由于多种原因,这个例子仍然是错误的。
您将遇到的另一个障碍是,要返回替换了类型变量的新类型,您需要自己实现ParameterizedType
,GenericArrayType
和WildcardType
,否则请使用未记录的sun.reflect
课程。
所有这一切都是说你应该只使用已经处理过这些东西的Guava,除非你出于某种原因编写自己的TypeToken
之类的东西。
对所有这一点的警告,您似乎已经知道,所有这一切都取决于在extends
子句中提供给超类型的实际类型参数(显式或隐式,如匿名)类)。如果您只是执行new Base<Double>()
,则无法在运行时恢复类型参数。
答案 1 :(得分:3)
诀窍是,从原始类获取类型参数,并将其与分析返回类型或类型参数时获得的类型变量相关联:
public class Scratch {
public static void main(String[] args) throws Exception {
Class clazz = Class.forName("net.redpoint.scratch.Derived");
Type superType = clazz.getGenericSuperclass();
if (superType instanceof ParameterizedType) {
ParameterizedType superPt = (ParameterizedType)superType;
Type[] typeArgs = superPt.getActualTypeArguments();
Type superRawType = superPt.getRawType();
if (superRawType instanceof Class) {
Class superRawClazz = (Class)superRawType;
TypeVariable[] typeParms = superRawClazz.getTypeParameters();
assert typeArgs.length == typeParms.length;
Map<TypeVariable,Type> typeMap = new HashMap<>();
for (int i = 0; i < typeArgs.length; i++) {
typeMap.put(typeParms[i], typeArgs[i]);
}
for (Method method : superRawClazz.getDeclaredMethods()) {
if (method.getName().equals("getE")) {
Type returnType = method.getGenericReturnType();
if (returnType instanceof TypeVariable) {
TypeVariable tv = (TypeVariable)returnType;
Type specializedType = typeMap.get(tv);
if (specializedType != null) {
// This generic parameter was replaced with an actual type
System.out.println(specializedType.toString());
} else {
// This generic parameter is still a variable
System.out.println(tv.getName());
}
}
}
}
}
}
}
}
答案 2 :(得分:0)
你的结局是错误的。 getE()
的签名已在 base 类中定义。
Derived
不会覆盖或以任何方式修改该方法。换句话说:该方法被定义为返回E
。扩展Base
并提供特定类型的事实不会影响该方法的定义!
因此,你试图进入&#34;改变了#34;返回类型是徒劳的。因为退货类型没有变化。相关 - 请看这里:
Class superClazz = Class.forName("Derived").getSuperclass();
for (TypeVariable clz : superClazz.getTypeParameters()) {
System.out.println(clz);
}
Class clazz = Class.forName("Derived");
Type superType = clazz.getGenericSuperclass();
if (superType instanceof ParameterizedType) {
ParameterizedType superPt = (ParameterizedType) superType;
for (Type t : superPt.getActualTypeArguments()) {
System.out.println(t.getTypeName());
}
}
以上版画:
E
F
java.lang.String
java.lang.Integer
(对于我使用两个参数E,F并将它们替换为String,Integer的重做示例)