[英]Java reflection: getting correct type of parameterized generic superclass method
我使用反射來發現類及其超類的方法,以及方法參數和返回值的類型。 這大部分都有效,但我遇到了一些特定的通用案例。 假設我有一個基類:
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 []有關,但我不知道如何到達那里。 請不要回應,除非你真的使用泛型反射。 我看過很多帖子都有“類型擦除可以防止這種情況”的答案,但事實並非如此。
我建議使用番石榴。 例如:
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
映射到從AbstractDerived
映射到T
,然后將T
映射到String
,因此該方法必須遞歸或迭代超類型。 我在這里有一個更復雜的例子,但由於種種原因,這個例子仍然是錯誤的。
您將遇到的另一個障礙是,要返回替換了類型變量的新類型,您需要自己實現ParameterizedType
, GenericArrayType
和WildcardType
,否則使用未記錄的sun.reflect
類。
所有這一切都是說你應該只使用已經處理過這些東西的Guava,除非你出於某種原因編寫自己的TypeToken
。
您似乎已經知道,對所有這一點的警告是,所有這些都取決於在extends
子句中提供給超類型的實際類型參數(在匿名類中顯式或隱式)。 如果您只是執行new Base<Double>()
則無法在運行時恢復類型參數。
訣竅是,從原始類獲取類型參數,並將其與分析返回類型或類型參數時獲得的類型變量相關聯:
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());
}
}
}
}
}
}
}
}
你的結局是錯誤的概念。 getE()
的簽名已在基類中定義。
Derived
不會覆蓋或以任何方式修改該方法。 換句話說:該方法被定義為返回E
擴展Base
並提供特定類型的事實不會影響該方法的定義!
因此,您嘗試進入“已更改”的返回類型是徒勞的。 因為返回類型沒有改變。 相關 - 請看這里:
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)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.