簡體   English   中英

Java反射:獲取正確類型的參數化通用超類方法

[英]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> {}

在這種情況下,您需要首先將EBase映射到從AbstractDerived映射到T ,然后將T映射到String ,因此該方法必須遞歸或迭代超類型。 在這里有一個更復雜的例子,但由於種種原因,這個例子仍然是錯誤的。

您將遇到的另一個障礙是,要返回替換了類型變量的新類型,您需要自己實現ParameterizedTypeGenericArrayTypeWildcardType ,否則使用未記錄的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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM