簡體   English   中英

在其抽象超類中使用任何嵌套子類的泛型類型

[英]Using a generic type of any nested subclass within its abstract superclass

假設您有以下抽象 java 類:

public abstract class AbstractRequestHandler<I,O> {
    I input;
    O output;
}

以及以下子類層次結構:

public abstract class AbstractUserRequestHandler<I extends User,O> extends AbstractRequestHandler<I,O>{...}
public abstract class AbstractUniversityRequestHandler<I extends UniversityUser> extends AbstractUserRequestHandler<I,String>{...}
public class StudentRequestHandler extends AbstractUniversityRequestHandler<Student>{...}
public class TeacherRequestHandler extends AbstractUniversityRequestHandler<Teacher>{...}

假設您需要在超類的給定點使用泛型類型,例如為了在構造函數上使用 gson 庫將請求 json 反序列化為特定的請求對象,如下所示:

public AbstractRequestHandler(final String inputJson) {
        input = new Gson().fromJson(inputJson,typeOfI);
}

您需要變量“typeOfI”中的泛型 I 類型

是否有一個全局解決方案允許獲取由遵守以下約束的具體子類指定的泛型類型?

  1. 無論子類層次結構如何,都會在運行時獲取類型(這也可能更復雜,作為此問題的示例)
  2. 開發人員只需要定義擴展超類的泛型,而無需在具體子類的某處手動指定泛型類型(例如在重寫的方法或構造函數上)

因此,如果您想定義一個新的具體子項,為泛型分配一個新值,您只需編寫以下具體類,例如:

public class StudentRequestHandler extends AbstractUniversityRequestHandler<Student>{

    public StudentRequestHandler(String inputJson) {
        super(inputJson);
    }

}

我找到了以下解決方案,但他們不尊重所要求的解決方案約束。

打破約束n°2的解決方案

一個解決方案可能是在超類上定義一個抽象方法,如下所示

protected abstract Type getRequestType();

然后在定義泛型的每個具體子類上實現它:

public class StudentRequestHandler extends AbstractUniversityRequestHandler<Student>{

    public StudentRequestHandler(String inputJson) {
        super(inputJson);
    }

    @Override
    protected Type getRequestType() {
        return Student.class;
    }
}

然后可以在目標超類的構造函數上使用getRequestType()方法:

public AbstractRequestHandler(final String inputJson) {
        request = new Gson().fromJson(inputJson,getRequestType());
}

但即使不管子類層次結構(尊重約束 n°1 )都能工作,開發人員也應該在每個具體的子類上手動實現一個抽象方法。

打破約束n°1的解決方案

如果層次結構很簡單,只有一個從目標超類擴展的直接子類,例如:

public class TeacherRequestHandler extends AbstractRequestHandler<Teacher,String>{...}

@naikus ( https://stackoverflow.com/users/306602/naikus ) 在以下 stackoverflow 線程上提出了一個可行的解決方案: 在其抽象超類中使用子類的泛型類型?

但是,如果具體類不是定義泛型的超類的直接子類(如在此問題上作為示例提出的那樣),則這不起作用。

編輯:在閱讀您的答案並測試了許多其他可能的情況后,我決定編輯您的代碼並重新編寫它以支持所有其他可能的邊緣情況,以包括跟蹤嵌套在其他泛型類型中的泛型。

遺憾的是,為了支持所有情況,我們需要比您提供的代碼多得多的代碼,泛型非常棘手,例如考慮這樣的類:

private class SomeClass<A, B, C, D, E, F> {}

private class SomeConfusingClass<A> extends SomeClass<List<Void>[], List<? extends A>[], List<? extends A[][][]>[][][], List<? extends String[]>[], Map<List<? extends A[]>, A[][]>[], A> {}

private class TestClass extends SomeConfusingClass<Void> {}

為了開始這樣做,我們需要有自己的 Java 泛型類型的實現,以便以后能夠構造像List<String>[]這樣的類型,因為無法使用原始 Java API 動態創建此類類型。
這是在像這樣的庫中處理泛型的非常流行的方式,您可以在 jackson 庫中看到類似的東西等等。
所以我們需要實現GenericArrayTypeParameterizedTypeWildcardType

private static class ResolvedGenericArrayType implements GenericArrayType {
    private final Type genericComponentType;

    ResolvedGenericArrayType(Type genericComponentType) {
        this.genericComponentType = genericComponentType;
    }

    @Override
    public Type getGenericComponentType() {
        return genericComponentType;
    }

    public String toString() {
        return getGenericComponentType().toString() + "[]";
    }

    @Override
    public boolean equals(Object o) {
        if (o instanceof GenericArrayType) {
            GenericArrayType that = (GenericArrayType) o;
            return Objects.equals(genericComponentType, that.getGenericComponentType());
        } else
            return false;
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(genericComponentType);
    }
}

private static class ResolvedParameterizedType implements ParameterizedType {
    private final Type[] actualTypeArguments;
    private final Class<?> rawType;
    private final Type ownerType;

    private ResolvedParameterizedType(Type rawType, Type[] actualTypeArguments, Type ownerType) {
        this.actualTypeArguments = actualTypeArguments;
        this.rawType = (Class<?>) rawType;
        this.ownerType = (ownerType != null) ? ownerType : this.rawType.getDeclaringClass();
    }

    public Type[] getActualTypeArguments() {
        return actualTypeArguments.clone();
    }

    public Class<?> getRawType() {
        return rawType;
    }

    public Type getOwnerType() {
        return ownerType;
    }

    @Override
    public boolean equals(Object o) {
        if (!(o instanceof ParameterizedType)) {
            return false;
        }
        ParameterizedType that = (ParameterizedType) o;
        if (this == that)
            return true;
        Type thatOwner = that.getOwnerType();
        Type thatRawType = that.getRawType();
        return Objects.equals(ownerType, thatOwner) && Objects.equals(rawType, thatRawType) &&
                Arrays.equals(actualTypeArguments, that.getActualTypeArguments());
    }

    @Override
    public int hashCode() {
        return Arrays.hashCode(actualTypeArguments) ^
                Objects.hashCode(ownerType) ^
                Objects.hashCode(rawType);
    }

    public String toString() {
        StringBuilder sb = new StringBuilder();
        if (ownerType != null) {
            sb.append(ownerType.getTypeName());
            sb.append("$");
            if (ownerType instanceof ResolvedParameterizedType) {
                sb.append(rawType.getName().replace(((ResolvedParameterizedType) ownerType).rawType.getName() + "$", ""));
            } else
                sb.append(rawType.getSimpleName());
        } else
            sb.append(rawType.getName());
        if (actualTypeArguments != null) {
            StringJoiner sj = new StringJoiner(", ", "<", ">");
            sj.setEmptyValue("");
            for (Type t : actualTypeArguments) {
                sj.add(t.getTypeName());
            }
            sb.append(sj.toString());
        }
        return sb.toString();
    }
}

private static class ResolvedWildcardType implements WildcardType {
    private final Type[] upperBounds;
    private final Type[] lowerBounds;

    public ResolvedWildcardType(Type[] upperBounds, Type[] lowerBounds) {
        this.upperBounds = upperBounds;
        this.lowerBounds = lowerBounds;
    }

    public Type[] getUpperBounds() {
        return upperBounds.clone();
    }

    public Type[] getLowerBounds() {
        return lowerBounds.clone();
    }

    public String toString() {
        Type[] lowerBounds = getLowerBounds();
        Type[] bounds = lowerBounds;
        StringBuilder sb = new StringBuilder();
        if (lowerBounds.length > 0)
            sb.append("? super ");
        else {
            Type[] upperBounds = getUpperBounds();
            if (upperBounds.length > 0 && !upperBounds[0].equals(Object.class)) {
                bounds = upperBounds;
                sb.append("? extends ");
            } else
                return "?";
        }
        StringJoiner sj = new StringJoiner(" & ");
        for (Type bound : bounds) {
            sj.add(bound.getTypeName());
        }
        sb.append(sj.toString());
        return sb.toString();
    }

    @Override
    public boolean equals(Object o) {
        if (o instanceof WildcardType) {
            WildcardType that = (WildcardType) o;
            return Arrays.equals(this.getLowerBounds(), that.getLowerBounds()) && Arrays.equals(this.getUpperBounds(), that.getUpperBounds());
        } else
            return false;
    }

    @Override
    public int hashCode() {
        Type[] lowerBounds = getLowerBounds();
        Type[] upperBounds = getUpperBounds();
        return Arrays.hashCode(lowerBounds) ^ Arrays.hashCode(upperBounds);
    }
} 

您基本上可以從 JDK 復制它們並進行一些清理。

我們需要的下一個實用程序是一個函數,如果我們做的一切正確,最后驗證,就像我們不想返回Map<List<? extends X>[]> Map<List<? extends X>[]>其中X仍未解析TypeVariable

private static boolean isDefined(Type type) {
    if (type instanceof Class) {
        return true;
    }
    if (type instanceof GenericArrayType) {
        return isDefined(((GenericArrayType) type).getGenericComponentType());
    }
    if (type instanceof WildcardType) {
        for (Type lowerBound : ((WildcardType) type).getLowerBounds()) {
            if (!isDefined(lowerBound)) {
                return false;
            }
        }
        for (Type upperBound : ((WildcardType) type).getUpperBounds()) {
            if (!isDefined(upperBound)) {
                return false;
            }
        }
        return true;
    }
    if (!(type instanceof ParameterizedType)) {
        return false;
    }
    for (Type typeArgument : ((ParameterizedType) type).getActualTypeArguments()) {
        if (!isDefined(typeArgument)) {
            return false;
        }
    }
    return true;
}

簡單的遞歸函數將為我們做到這一點。 我們只是檢查每個可能的泛型類型並檢查它的每個成員是否也被定義,除非我們能找到一些隱藏的TypeVariable ,否則我們沒問題。
Main 函數可以與您的代碼保持一致,我們只會在最后編輯那個檢查以使用我們的新函數:

public static Type getParameterizedType(Class<?> klass, Class<?> rootClass, int paramTypeNumber) throws GenericsException {

    int targetClassParametersNumber = rootClass.getTypeParameters().length;
    if (targetClassParametersNumber == 0) {
        throw new GenericsException(String.format("Target class [%s] has no parameters type", rootClass.getName()));
    } else if (targetClassParametersNumber - 1 < paramTypeNumber)
        throw new GenericsException(String.format("Target class [%s] has parameters type which index start from [0] to [%s]. You requested instead parameter with index [%s]", rootClass, paramTypeNumber - 1, targetClassParametersNumber));

    Type type = analyzeParameterizedTypes(klass, klass, rootClass, paramTypeNumber, null);
    if (!isDefined(type))
        throw new GenericsException(String.format("Parameter [%s] with index [%d] defined on class [%s] has not been valued yet on child class [%s]", type, paramTypeNumber, rootClass.getName(), klass.getName()));
    return type;
}

現在讓我們處理我們的主要工作

public static Type analyzeParameterizedTypes(final Class<?> klass, final Class<?> targetClass, final Class<?> rootClass, final int paramTypeNumber, Map<Integer, Type> childClassTypes) throws GenericsException { 

函數,begging 保持不變,我們將所有TypeVariable收集到簡單映射,保留從前一個類上的前一個循環中收集到的信息。

    Type superclassType = klass.getGenericSuperclass();
    Map<TypeVariable<?>, Type> currentClassTypes = new HashMap<>();
    int z = 0;
    if (childClassTypes != null) {
        for (TypeVariable<?> variable : klass.getTypeParameters()) {
            currentClassTypes.put(variable, childClassTypes.get(z));
            z++;
        }
    }

然后我們讓循環收集和改進我們的類型參數:

    Map<Integer, Type> superClassesTypes = new HashMap<>();
    if (superclassType instanceof ParameterizedType) {
        int i = 0;
        for (final Type argType : ((ParameterizedType) superclassType).getActualTypeArguments()) {
            if (argType instanceof TypeVariable) {
                superClassesTypes.put(i, currentClassTypes.containsKey(argType) ? currentClassTypes.get(argType) : argType);
            } else {
                superClassesTypes.put(i, refineType(klass, argType, currentClassTypesByName));
            }
            i++;
        }
    }

每個類型參數有 2 個路徑,如果它的 TypeVariable 我們只是繼續跟蹤它,如果它還有其他任何東西,我們嘗試從對TypeVariable任何可能引用中“精煉”它。 這是這段代碼中最復雜的過程,這就是為什么我們需要上面所有這些類的原因。
我們從這個處理所有可能類型的簡單遞歸調度方法開始:

private static Type refineType(Type type, Map<TypeVariable<?>, Type> typeVariablesMap) throws GenericsException {
    if (type instanceof Class) {
        return type;
    }
    if (type instanceof GenericArrayType) {
        return refineArrayType((GenericArrayType) type, typeVariablesMap);
    }
    if (type instanceof ParameterizedType) {
        return refineParameterizedType((ParameterizedType) type, typeVariablesMap);
    }
    if (type instanceof WildcardType) {
        return refineWildcardType((WildcardType) type, typeVariablesMap);
    }
    if (type instanceof TypeVariable) {
        return typeVariablesMap.get(type);
    }
    throw new GenericsException("Unsolvable generic type: " + type);
}

以及在類型數組上運行它的小實用方法:

private static Type[] refineTypes(Type[] types, Map<TypeVariable<?>, Type> typeVariablesMap) throws GenericsException {
    Type[] refinedTypes = new Type[types.length];
    for (int i = 0; i < types.length; i++) {
        refinedTypes[i] = refineType(types[i], typeVariablesMap);
    }
    return refinedTypes;
}

每個類型都有自己的函數,或者如果它的TypeVariable我們只是從地圖中獲取一個已解決的。 注意這可以返回null,這里我沒有處理。 這可以在以后改進。 對於類,我們不需要做任何事情,所以我們可以只返回類本身。

對於GenericArrayType我們需要首先找出這樣的數組可能有多少維(這也可以在我們的精煉方法中通過遞歸處理,但在我看來調試起來有點困難):

private static int getArrayDimensions(GenericArrayType genericArrayType) {
    int levels = 1;
    GenericArrayType currentArrayLevel = genericArrayType;
    while (currentArrayLevel.getGenericComponentType() instanceof GenericArrayType) {
        currentArrayLevel = (GenericArrayType) currentArrayLevel.getGenericComponentType();
        levels += 1;
    }
    return levels;
}

然后我們要提取數組的嵌套組件類型,因此對於List<A>[][][]我們只需要List<A>

private static Type getArrayNestedComponentType(GenericArrayType genericArrayType) {
    GenericArrayType currentArrayLevel = genericArrayType;
    while (currentArrayLevel.getGenericComponentType() instanceof GenericArrayType) {
        currentArrayLevel = (GenericArrayType) currentArrayLevel.getGenericComponentType();
    }
    return currentArrayLevel.getGenericComponentType();
}

然后我們需要細化這個類型,所以我們的List<A>將更改為例如List<String>

    Type arrayComponentType = refineType(getArrayNestedComponentType(genericArrayType), typeVariablesMap);

並使用精煉類型重建我們的通用結構,因此我們創建的List<String>將變回List<String>[][][]

private static Type buildArrayType(Type componentType, int levels) throws GenericsException {
    if (componentType instanceof Class) {
        return Array.newInstance(((Class<?>) componentType), new int[levels]).getClass();
    } else if (componentType instanceof ParameterizedType) {
        GenericArrayType genericArrayType = new ResolvedGenericArrayType(componentType);
        for (int i = 1; i < levels; i++) {
            genericArrayType = new ResolvedGenericArrayType(genericArrayType);
        }
        return genericArrayType;
    } else {
        throw new GenericsException("Array can't be of generic type");
    }
}

整個函數看起來像這樣:

private static Type refineArrayType( GenericArrayType genericArrayType, Map<TypeVariable<?>, Type> typeVariablesMap) throws GenericsException {
    int levels = getArrayDimensions(genericArrayType);
    Type arrayComponentType = refineType(getArrayNestedComponentType(genericArrayType), typeVariablesMap);
    return buildArrayType(arrayComponentType, levels);
}

對於ParameterizedType更簡單,我們只是細化類型參數,並使用這些細化的參數創建新的ParameterizedType實例:

private static Type refineParameterizedType(ParameterizedType parameterizedType, Map<TypeVariable<?>, Type> typeVariablesMap) throws GenericsException {
    Type[] refinedTypeArguments = refineTypes(parameterizedType.getActualTypeArguments(), typeVariablesMap);
    return new ResolvedParameterizedType(parameterizedType.getRawType(), refinedTypeArguments, parameterizedType.getOwnerType());
}

WildcardType相同:

private static Type refineWildcardType(WildcardType wildcardType, Map<TypeVariable<?>, Type> typeVariablesMap) throws GenericsException {
    Type[] refinedUpperBounds = refineTypes(wildcardType.getUpperBounds(), typeVariablesMap);
    Type[] refinedLowerBounds = refineTypes(wildcardType.getLowerBounds(), typeVariablesMap);
    return new ResolvedWildcardType(refinedUpperBounds, refinedLowerBounds);
}

這讓我們的整個分析功能看起來像這樣:

public static Type analyzeParameterizedTypes(final Class<?> klass, final Class<?> targetClass, final Class<?> rootClass, final int paramTypeNumber, Map<Integer, Type> childClassTypes) throws GenericsException {
    Type superclassType = klass.getGenericSuperclass();
    Map<TypeVariable<?>, Type> currentClassTypes = new HashMap<>();
    int z = 0;
    if (childClassTypes != null) {
        for (TypeVariable<?> variable : klass.getTypeParameters()) {
            currentClassTypes.put(variable, childClassTypes.get(z));
            z++;
        }
    }

    Map<Integer, Type> superClassesTypes = new HashMap<>();
    if (superclassType instanceof ParameterizedType) {
        int i = 0;
        for (final Type argType : ((ParameterizedType) superclassType).getActualTypeArguments()) {
            if (argType instanceof TypeVariable) {
                superClassesTypes.put(i, currentClassTypes.getOrDefault(argType, argType));
            } else {
                superClassesTypes.put(i, refineType(argType, currentClassTypes));
            }
            i++;
        }
    }

    if (klass != rootClass) {
        final Class<?> superClass = klass.getSuperclass();
        if (superClass == null)
            throw new GenericsException(String.format("Class [%s] not found on class parent hierarchy [%s]", rootClass, targetClass));
        return analyzeParameterizedTypes(superClass, targetClass, rootClass, paramTypeNumber, superClassesTypes);
    }
    return childClassTypes.get(paramTypeNumber);

}

用法示例:

private class SomeClass<A, B, C, D, E, F> {}
private class SomeConfusingClass<A> extends SomeClass<List<Void>[], List<? extends A>[], List<? extends A[][][]>[][][], List<? extends String[]>[], Map<List<? extends A[]>, A[][]>[], A> {}
private class TestClass extends SomeConfusingClass<Void> {}

public static void main(String[] args) throws Exception {
    System.out.println(GenericsUtils.getParameterizedType(TestClass.class, SomeClass.class, 0));
    System.out.println(GenericsUtils.getParameterizedType(TestClass.class, SomeClass.class, 1));
    System.out.println(GenericsUtils.getParameterizedType(TestClass.class, SomeClass.class, 2));
    System.out.println(GenericsUtils.getParameterizedType(TestClass.class, SomeClass.class, 3));
    System.out.println(GenericsUtils.getParameterizedType(TestClass.class, SomeClass.class, 4));
    System.out.println(GenericsUtils.getParameterizedType(TestClass.class, SomeClass.class, 5));
}

結果:

java.util.List<java.lang.Void>[]
java.util.List<? extends java.lang.Void>[]
java.util.List<? extends java.lang.Void[][][]>[][][]
java.util.List<? extends java.lang.String[]>[]
java.util.Map<java.util.List<? extends java.lang.Void[]>, java.lang.Void[][]>[]
class java.lang.Void

可以在此處找到帶有測試的完整代碼: https : //gist.github.com/GotoFinal/33b9e282f270dbfe61907aa830c27587或此處: https : //github.com/GotoFinal/generics-utils/tree/edge-cases-1

基於 OP 原始答案代碼,但涵蓋了大多數邊緣情況。

您不能在沒有繼承的情況下獲取通用參數,因為Java中的通用類型具有擦除類型: https : //docs.oracle.com/javase/tutorial/java/generics/erasure.html

您需要采取在問題中已經分享的方法。 使用繼承和ParameterizedType:

(Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];

答案是:Java 不支持具體泛型,請參閱2004 年的此功能請求,其中有很多重復項。 也可以看看:

因此,除非您想切換到 Kotlin,否則您無能為力,因為 Java 中的泛型類型信息僅對編譯器可用,而不是在運行時(具體化泛型)期間可用。

如果您不喜歡這個答案,我很抱歉,但從 2020 年初的 Java 13 開始,它仍然是正確的。

我認為一個可行的解決方案是擴展@naikus 提出的解決方案。 它只需要在構造函數的層次結構中向上。

import java.lang.reflect.ParameterizedType;

public abstract class AbstractRequestHandler<I,O> {

    protected I input;
    protected O output;

    protected Class<I> inputClass;
    protected Class<O> outputClass;

    protected AbstractRequestHandler() {
        Class<?> clazz = getClass();
        while (!clazz.getSuperclass().equals(AbstractRequestHandler.class)) {
            clazz = clazz.getSuperclass();
        }
        ParameterizedType genericSuperclass = (ParameterizedType) clazz.getGenericSuperclass();
        this.inputClass = (Class<I>) genericSuperclass.getActualTypeArguments()[0];
        this.outputClass = (Class<O>) genericSuperclass.getActualTypeArguments()[1];
    }
}

我曾研究過一個實用程序庫,該提供了一種方法,該方法通常可以解決遞歸分析所有父類層次結構以獲得特定泛型類型的問題。

它可以在我的 GitHub 項目上找到: https : //github.com/gregorycallea/generics-utils

更新:感謝@GoToFinal用戶通過他的巨大努力改進了項目,還涵蓋了幾種不同的復雜泛型案例(例如GenericArrayTypeParameterizedTypeWildcardType )。 有關這些改進的所有詳細信息,請參閱他對這個問題的回答。


這是該方法適用的總結場景:

  1. 假設您有一個參數化的類,定義了未定義數量的泛型。

示例:讓我們將以下定義 3 個泛型的“ Base ”類視為根類:

 private class Base<I, E, F> { I var1; E var2; F var3; }

注意:為每個泛型分配一個從 0 開始的索引。因此該類的索引映射是:

 I = 0 E = 1 F = 2
  1. 假設這個根類有一個復雜的層次的子類層次結構。

例子:

 // Base<I,E,F> // BaseA<G,H> extends Base<H,Boolean,G> // BaseB<T> extends BaseA<T,String> // BaseC<H> extends BaseB<H> // BaseD extends BaseC<Integer> // BaseE extends BaseD // BaseF extends BaseE // BaseG extends BaseF // BaseH<H> extends BaseG<H,Double> // BaseI<T> extends BaseF<T> // BaseL<J> extends BaseI<J> // BaseM extends BaseL<Float> // BaseN extends BaseM

注意:請注意,在子層次結構中定義了新的參數化類,並且一些類根本沒有參數化

  1. 然后假設您想選擇根類子層次結構上的任何類,然后從這里開始獲取在類上定義的特定泛型的確切類型。

示例:您想知道從子類BaseN開始的Base類上定義的E泛型(索引 = 1)的類型。

為此,您可以簡單地執行GenericsUtils.getParameterizedType方法,如下所示:

Type targetType = GenericsUtils.getParameterizedType(GenericChildClass.class, RootClass.class, genericRootClassIndex);

例子:

 Type EType = GenericsUtils.getParameterizedType(BaseN.class, Base.class, 1);

我使用單元測試評估了此示例場景的幾個案例。 看看: https : //github.com/gregorycallea/generics-utils/blob/master/src/test/java/com/github/gregorycallea/generics/GenericsUtilsTest.java


關於在我的問題中公開的初始場景,我們可以在 AbstractRequestHandler 構造函數上使用此方法,如下所示:

public abstract class AbstractRequestHandler<I,O> {
        I input;
        O output;

        public AbstractRequestHandler(String inputJson) throws GenericsException {
            this.input = new Gson().fromJson(inputJson,GenericsUtils.getParameterizedType(getClass(), AbstractRequestHandler.class, 0));
        }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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