У меня есть свойство bean-компонента типа List<? extends Number> и список возможных классов реализации с их конструкторами. Я рекурсивно ищу кандидатов в параметры конструктора.
Один из кандидатов в конструкторы - ArrayList(Collection<? extends E>).
Я пытаюсь разрешить параметры типа и параметры конструктора с помощью Guava, но когда есть цепочка подстановочных типов, я получаю что-то вроде: ? extends capture#3-of ? extends capture#2-of ? extends capture#1-of ? extends ...
public static <T extends Object> void main(String[] args) throws Exception {
TypeToken<?> propTt = new TypeToken<List<? extends Number>>() {
};
TypeToken<?> candidate = propTt.getSubtype(ArrayList.class);
TypeToken<?> constructorResult;
Constructor<?> cons = ArrayList.class.getConstructor(Collection.class);
// java.util.ArrayList<? extends java.lang.Number>
constructorResult = candidate.constructor(cons).getReturnType();
System.out.println(constructorResult);
// java.util.Collection<? extends E>
Type param = cons.getGenericParameterTypes()[0];
System.out.println(param);
// java.util.Collection<? extends capture#1-of ? extends class java.lang.Number>
TypeToken<?> resolvedParam = constructorResult.resolveType(param);
System.out.println(resolvedParam);
}
Это происходит, хотя мы не можем использовать подстановочные знаки в качестве параметров типа для new. Следующее является незаконным:
List<?> x = new ArrayList<?>();
List<? extends Number> y = new ArrayList<? extends Number>();
Вместо этого мы пишем:
List<?> x = new ArrayList<Object>();
List<? extends Number> y = new ArrayList<Number>();
или автоматизировать последнее с помощью оператора ромба.
Желаемый тип resolvedParam - это java.util.Collection<? extends java.lang.Number>.
com.google.common.reflect.Types имеет метод newParameterizedType(), в котором я мог передать прежние подстановочные знаки, вручную разрешенные для параметризованных типов или классов, но этот метод является частным для пакета. Я не уверен в своем обходном пути:
package com.google.common.reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.checkerframework.checker.nullness.qual.Nullable;
@SuppressWarnings("serial")
public class TestWildcard {
public static <T extends Object> void main(String[] args) throws Exception {
TypeToken<?> propTt = new TypeToken<List<? extends Number>>() {
};
TypeToken<?> candidate = propTt.getSubtype(ArrayList.class);
TypeToken<?> constructorResult;
Constructor<?> cons = ArrayList.class.getConstructor(Collection.class);
// new java.util.ArrayList<java.lang.Number>();
constructorResult = resolveConstructorResult(candidate);
System.out.println(constructorResult);
// java.util.Collection<? extends E>
Type param = cons.getGenericParameterTypes()[0];
System.out.println(param);
// java.util.Collection<? extends capture#1-of ? extends class java.lang.Number>
TypeToken<?> resolvedParam = constructorResult.resolveType(param);
System.out.println(resolvedParam);
}
static TypeToken<?> resolveConstructorResult(TypeToken<?> candidate) {
Type ct = candidate.getType();
if (ct instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) ct;
Class<?> ptClazz = ((Class<?>) pt.getRawType());
TypeVariable<?>[] tvars = ptClazz.getTypeParameters();
Type[] targs = pt.getActualTypeArguments();
boolean doIt = false;
for (int i = 0; i < targs.length; i++) {
if (targs[i] instanceof WildcardType) {
WildcardType wt = (WildcardType) targs[i];
TypeToken<?> ubound1 = TypeToken.of(wt.getUpperBounds()[0]);
TypeToken<?> ubound2 = TypeToken.of(tvars[i].getBounds()[0]);
if (ubound1.isSubtypeOf(ubound2)) {
doIt = true;
targs[i] = ubound1.getType();
} else if (ubound2.isSubtypeOf(ubound1)) {
doIt = true;
targs[i] = ubound2.getType();
}
}
}
if (doIt) {
pt = GuavaReflectAccessHelper.newParameterizedTypeWithOwner(pt.getOwnerType(), ptClazz, targs);
candidate = TypeToken.of(pt);
}
}
return candidate;
}
private static class GuavaReflectAccessHelper {
public static ParameterizedType newParameterizedTypeWithOwner(@Nullable Type ownerType, Class<?> rawType,
Type... arguments) {
return Types.newParameterizedTypeWithOwner(ownerType, rawType, arguments);
}
}
}




Преобразование типа подстановочного знака в ParameterizedType звучит здесь не очень хорошо, но вы можете просто реализовать этот интерфейс самостоятельно.
Или используйте другую библиотеку, которая поддерживает более динамическое создание:
https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/reflect/TypeUtils.html
И, наверное, многие другие библиотеки.
В гуаве вы можете делать только что-то вроде этого:
static <K, V> TypeToken<Map<K, V>> mapToken(TypeToken<K> keyToken, TypeToken<V> valueToken) {
return new TypeToken<Map<K, V>>() {}
.where(new TypeParameter<K>() {}, keyToken)
.where(new TypeParameter<V>() {}, valueToken);
}
...
TypeToken<Map<String, BigInteger>> mapToken = mapToken(
TypeToken.of(String.class),
TypeToken.of(BigInteger.class));
TypeToken<Map<Integer, Queue<String>>> complexToken = mapToken(
TypeToken.of(Integer.class),
new TypeToken<Queue<String>>() {});
Тебе этого может хватить.
Вы правы, но если вы нормализуете свои дженерики до типа ParameterizedType, вы можете получить такой тип: <T extends SomeClass & SomeInterface> List<T> (как тип возвращаемого метода или T из класса и как поле) Но да, я забыл, что они добавили поддержку только для верхнего / API отражения нижних границ, но не на самом языке, но вы все равно должны подумать о случае, о котором я упоминал выше.
Как переписать mapToken(), чтобы он принимал произвольный необработанный класс и массив аргументов фактического типа? TypeParameter.of(Type) отсутствует, а TypeToken.of(Class) возвращает токен необработанного типа, а не параметризованный тип, необходимый для where().
TypeToken.of(Object.class).getSubtype(Map.class)?
Я не думаю, что это возможно с этой библиотекой, по крайней мере, я не вижу никакого способа, вы можете использовать другую
Пользовательский newParameterizedType может быть реализован с TypeResolver.where() (не путать с TypeToken.where()). Соответствующая часть в моем resolveConstructorResult() в вопросе может быть изменена следующим образом:
if (doIt) {
pt = (ParameterizedType)newParameterizedType(ptClazz, targs);
candidate = TypeToken.of(pt);
}
}
return candidate;
}
public static Type newParameterizedType(Class<?> cls, Type... args) {
TypeResolver resolver = new TypeResolver();
TypeVariable<?>[] tvars = cls.getTypeParameters();
for (int i = 0; i < args.length; i++) {
resolver = resolver.where(tvars[i], args[i]);
}
return resolver.resolveType(dumbToGenericType(cls).getType());
}
@SuppressWarnings("unchecked")
public static <T> TypeToken<? extends T> dumbToGenericType(Class<T> cls) {
return (TypeToken<T>)TypeToken.of(Object.class).getSubtype(cls);
}
Все хорошо, но
List<? extends SomeClass & SomeInterface>существовать не может. Подстановочные знаки всегда имеют одну верхнюю границу.