Как получить параметр типа REAL подкласса List? - PullRequest
0 голосов
/ 22 февраля 2019

Когда я читаю исходный код JavaFX, в com.sun.javafx.fxml.BeanAdapter статический метод getGenericListItemType(Type):

Определяет тип элемента списка.

Однако в простых тестовых случаях я обнаружил, что эта реализация может быть ошибочной:

abstract class A<T> implements List<String> {}

abstract class B extends A<Integer> {}

abstract class C<S, T> implements List<T> {}

abstract class D extends C<String, Integer> {}

// in a function scope
BeanAdapter.getGenericListItemType(B.class) // expects String, gets Integer
BeanAdapter.getGenericListItemType(A.class) // works correctly

BeanAdapter.getGenericListItemType(D.class) // expects Integer, gets String

Оказывается, что эта некорректная реализация может обрабатывать только идеальные условия с шаблоном List<T> ← A<T> ← B<T> ← ... (parameterized) ... ← Y ← Z

Однако, когда я попытался реализовать функцию, я обнаружил, что это довольно сложно.Интересно, возможно ли получить реальный параметр типа.И, если возможно, не могли бы вы дать несколько подсказок или фрагмент?

1 Ответ

0 голосов
/ 22 февраля 2019

Да, это возможно.

Вы можете создать Map<TypeVariable<?>, Class<?>> и пройти граф типов, начиная с указанного корневого класса, чтобы заполнить конкретные типы, предоставленные для каждой переменной типа.Например:

public static Map<TypeVariable<?>, Class<?>> findTypeParameterValues(Class<?> cls) {
    Map<TypeVariable<?>, Class<?>> parameterMap = new HashMap<>();
    findTypeParameterValues(parameterMap, cls);     
    return parameterMap;
}

private static void findTypeParameterValues(Map<TypeVariable<?>, Class<?>> map, Class<?> cur) {
    // create list of parameterized super types
    List<ParameterizedType> pTypes = genericSuperTypes(cur)
        .filter(ParameterizedType.class::isInstance)
        .map(ParameterizedType.class::cast)
        .collect(Collectors.toList());

    for(ParameterizedType pType : pTypes) {     
        Type[] tArgs = pType.getActualTypeArguments(); // provided type arguments
        Class<?> rawType = (Class<?>) pType.getRawType(); // (always a Class<?>)
        TypeVariable<?>[] tParams = rawType.getTypeParameters(); // type parameters

        for(int i = 0; i < tArgs.length; i++) { // iterate over both
            Type tArg = tArgs[i]; // match type argument...
            TypeVariable<?> tParam = tParams[i]; // with type parameter
            Class<?> arg; // the value for the parameter
            if(tArg instanceof Class<?>) {
                 // concrete argument
                arg = (Class<?>) tArg;
            } else if(tArg instanceof TypeVariable<?>) {
                 // concrete argument provided previously, or null
                arg = map.get((TypeVariable<?>) tArg);
            } else {
                throw new UnsupportedOperationException("Unsupported type argument type: " + tArg);
            }
            map.put(tParam, arg); // put found value in map
        }
    }

    superClasses(cur).forEach(pt -> findTypeParameterValues(map, pt));
}

private static Stream<Class<?>> superClasses(Class<?> cls) {        
    Stream.Builder<Class<?>> ret = Stream.builder();
    ret.add(cls.getSuperclass());
    Arrays.stream(cls.getInterfaces()).forEach(ret::add);
    return ret.build().filter(Objects::nonNull);
}

private static Stream<Type> genericSuperTypes(Class<?> cls) {
    Stream.Builder<Type> ret = Stream.builder();
    ret.add(cls.getGenericSuperclass());
    Arrays.stream(cls.getGenericInterfaces()).forEach(ret::add);
    return ret.build().filter(Objects::nonNull);
}

Затем, чтобы использовать результат для вашего конкретного случая, вы можете получить конкретное значение для переменной типа List с карты:

public static void main(String[] args) throws Throwable {
    System.out.println(getGenericListItemType(B.class)); // class java.lang.String
    System.out.println(getGenericListItemType(A.class)); // class java.lang.String
    System.out.println(getGenericListItemType(D.class)); // class java.lang.Integer
}

private static final TypeVariable<?> listE = List.class.getTypeParameters()[0];

public static Class<?> getGenericListItemType(Class<?> cls) {  
    // method defined in 'Reflection' helper class      
    return Reflection.findTypeParameterValues(cls).get(listE);
}

Обратите внимание, что еслинет конкретного определения для конкретной переменной типа, карта будет содержать null в качестве значения для переменной типа.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...