Извлечение параметров типа из экземпляра базового базового интерфейса
Для двух интерфейсов:
public interface BaseInterface<T> { }
public interface ExtendedInterface<T0, T1> extends BaseInterface<T0> {}
и конкретный класс:
public class MyClass implements ExtendedInterface<String, Object> { }
Как узнать параметр типа, переданный на интерфейс BaseInterface?
(Я могу получить параметры типа ExtendedInterface, вызвав что-то вроде
MyClass.class.getGenericInterfaces()[0].getActualTypeArguments()
но я не могу определить простой способ рекурсии в базовые общие интерфейсы и получить что-нибудь значимое).
Ответы
Ответ 1
Эту проблему нелегко полностью решить в целом. Например, вы также должны учитывать параметры типа содержащегося класса, если это внутренний класс,...
Поскольку отражение по родовым типам настолько сложно, используя только то, что сама Java предоставляет, я написал библиотеку, которая выполняет тяжелую работу: gentyref. См. http://code.google.com/p/gentyref/
Для вашего примера, используя gentyref, вы можете:
Type myType = MyClass.class;
// get the parameterized type, recursively resolving type parameters
Type baseType = GenericTypeReflector.getExactSuperType(myType, BaseInterface.class);
if (baseType instanceof Class<?>) {
// raw class, type parameters not known
// ...
} else {
ParameterizedType pBaseType = (ParameterizedType)baseType;
assert pBaseType.getRawType() == BaseInterface.class; // always true
Type typeParameterForBaseInterface = pBaseType.getActualTypeArguments()[0];
System.out.println(typeParameterForBaseInterface);
}
Ответ 2
Я не знаю, что именно вы пытаетесь достичь, и что известно, а что нет, но вы можете повторить это суперинтерфейс следующим образом:
Type[] interfaces = MyClass.class.getGenericInterfaces();
ParameterizedType extInterfaceType = (ParameterizedType)interfaces[0];
Class<?> extInterfaceClass = (Class<?>)extInterfaceType.getRawType();
Type[] baseInterfaces = extInterfaceClass.getGenericInterfaces();
ParameterizedType baseInterfaceType = (ParameterizedType)baseInterfaces[0];
Class<?> baseInterfaceClass = (Class<?>)baseInterfaceType.getRawType();
Конечно, если вы достигнете второго уровня таким образом, вы получите только свои имена T0 и T1 в качестве общих параметров. Если вы знаете взаимосвязь между ExtendedInterface
и BaseInterface
, вам действительно не нужно заходить так далеко, поскольку вы знаете, какой общий параметр первого передается последнему. Если нет, вам, вероятно, придется пройти через их параметры и найти совпадение. Что-то на основе этого возможно:
Type[] params = extInterfaceClass.getTypeParameters();
for (Type param : params) {
if (param == baseInterfaceType.getActualTypeArguments()[0]) {
// ...
}
}
Ответ 3
Это трудно решить, используя API Java Reflection, потому что нужно разрешить все встреченные переменные типа. Guava с версии 12 имеет TypeToken класс, который содержит полностью разрешенную информацию о типе.
В вашем примере вы можете:
TypeToken<? extends T> token = TypeToken.of(MyClass.class);
ParameterizedType type =
(ParameterizedType) token.getSupertype(BaseInterface.class).getType();
Type[] parameters = type.getActualTypeArguments();
Тем не менее вам нужно помнить, что это работает только в случаях, когда MyClass не является общим. В противном случае значение параметров типа недоступно во время выполнения из-за стирания типа.
Ответ 4
Я не думаю, что существует прямой способ получить общий тип базового интерфейса.
Одним из способов было бы объявить метод в интерфейсе следующим образом:
public interface BaseInterface<T> {
Class<T> getGenericClass();
}
Кроме того, я не знаю, какой у вас контроль над этими классами. Вы всегда можете утверждать, что все исполнители имеют базовый интерфейс, явно объявленный как:
public class MyClass implements ExtendedInterface<String, Object>, BaseInterface<String>{ }
и
MyClass.class.getGenericInterfaces()[1].getActualTypeArguments()[0]
Ответ 5
Этот вид делает то, что вам нужно, но это все еще не так. Например, он не обрабатывает случай, когда Foo<T> implements Bar<Map<T>>
. То, что вам действительно нужно, это какой-то способ задать jvm "ok", вот список типов. Какой фактический тип я могу получить, если я применил их к этому родовому типу? "
Но этот код делает то, что вам нужно.
import java.lang.reflect.GenericDeclaration;
import java.lang.reflect.ParameterizedType;
import java.util.*;
interface BaseInterface<T> {}
interface FirstArg<T1,T2> extends BaseInterface<T1>{}
interface SecondArg<T1,T2> extends BaseInterface<T2>{}
class First implements FirstArg<Number, String> {}
class Second implements SecondArg<Number, String> {}
public class Example {
public static void main(String[] av) {
new Example().go();
}
void go() {
test(First.class);
test(Second.class);
}
void test(Class<?> c1) {
ParameterizedType t2 = (ParameterizedType) c1.getGenericInterfaces()[0];
System.out.println(c1 + " implements " + t2 );
Class<?> c2 = (Class<?>)t2.getRawType();
GenericDeclaration g2 = (GenericDeclaration) c2;
System.out.println(t2 + " params are " + Arrays.asList(g2.getTypeParameters()));
System.out.println("So that means");
for(int i = 0; i<t2.getActualTypeArguments().length; i++) {
System.out.println("Parameter " + c2.getTypeParameters()[i] + " is " + t2.getActualTypeArguments()[i]);
}
ParameterizedType t3 = (ParameterizedType) c2.getGenericInterfaces()[0];
System.out.println(t2 + " implements " + t3);
System.out.println("and so that means we are talking about\n" + t3.getRawType().toString() + " <");
for(int i = 0 ; i< t3.getActualTypeArguments().length; i++) {
System.out.println("\t" + t3.getActualTypeArguments()[i] + " -> "
+ Arrays.asList(g2.getTypeParameters()).indexOf(t3.getActualTypeArguments()[i])
+ " -> " +
t2.getActualTypeArguments()[Arrays.asList(g2.getTypeParameters()).indexOf(t3.getActualTypeArguments()[i])]
);
}
System.out.println(">");
System.out.println();
}
}
Ответ 6
Я не думаю, что вы можете, поскольку они действительно специфичны для конкретного экземпляра, а не для класса. Рассмотрим следующее:
List<String> a = new ArrayList<String>();
Тот факт, что a является общим списком строк, специфичен для экземпляра a, а не для класса List. Таким образом, ни один из методов объекта List.class не может сказать вам, что обобщенный тип будет иметь тип String для a. Хотя MyClass в вашем примере имеет установленные значения для жанровых типов интерфейса, я не думаю, что это было бы доступно в экземпляре объекта класса интерфейса.
Ответ 7
Я думаю, что единственным вариантом, о котором я могу думать, является проверка универсального метода, объявленного BaseInterface
, а не переопределенного.
Ответ 8
плохой этикет снова от меня, отвечая на мой собственный вопрос.
Как отметил Гикс, когда вы начинаете подниматься по иерархии родовых типов, за исключением первого, вы теряете информацию о аргументах типа.
Но важными битами являются: вы получаете параметры типа первого универсального интерфейса, который должен быть инстансом (в моем примере ExtendedInterface), и вы также получаете имена параметров типа, используемых для создания суб-интерфейсов.
Таким образом, можно определить аргументы типа для базовых интерфейсов, сохранив карту имен TypeVariable для аргументов фактического типа.
Я обновлю код позже, но он работает (вы можете определить параметр типа, используемый для экземпляра BaseInterface, из MyClass.class).
Обновление
Это первый проход, на котором зеленые индикаторы просты. Он нуждается в работе... Реальный вопрос заключается в том, что проблема заслуживает такого смехотворного решения?
public class GenericReflectionUtils
{
@SuppressWarnings("unchecked")
public static List<Class> getGenericInterfaceTypeArguments(Class baseInterface, Class concreteClass)
{
if (!baseInterface.isAssignableFrom(concreteClass))
{
throw new IllegalArgumentException("Illegal base interface argument");
}
if (concreteClass.getTypeParameters().length > 0)
{
throw new IllegalArgumentException("Can't determine the type arguments of a generic interface of a generic class");
}
for (Type genericInterface : concreteClass.getGenericInterfaces())
{
List<Class> result = null;
if (genericInterface instanceof Class)
{
result = getGenericInterfaceTypeArguments(baseInterface,(Class)genericInterface);
}
else
{
result = getGenericInterfaceTypeArguments(baseInterface, (ParameterizedType)genericInterface);
}
if (result != null)
{
return result;
}
}
return null;
}
public static Class getClass(Type type)
{
if (type instanceof Class)
{
return (Class) type;
}
if (type instanceof ParameterizedType)
{
return getClass(((ParameterizedType) type).getRawType());
}
if (type instanceof GenericArrayType)
{
Type componentType = ((GenericArrayType) type).getGenericComponentType();
Class<?> componentClass = getClass(componentType);
if (componentClass != null)
{
return Array.newInstance(componentClass, 0).getClass();
}
return null;
}
return null;
}
@SuppressWarnings("unchecked")
private static List<Class> getGenericInterfaceTypeArguments(Class baseInterface, ParameterizedType currentType)
{
Class currentClass = getClass(currentType);
if (!baseInterface.isAssignableFrom(currentClass))
{
// Early out - current type is not an interface that extends baseInterface
return null;
}
Type[] actualTypeArguments = currentType.getActualTypeArguments();
if (currentClass == baseInterface)
{
// currentType is a type instance of the base generic interface. Read out the type arguments and return
ArrayList<Class> typeArgs = new ArrayList<Class>(actualTypeArguments.length);
for (Type typeArg : actualTypeArguments)
{
typeArgs.add(getClass(typeArg));
}
return typeArgs;
}
// currentType is derived
Map<String, Class> typeVarMap = createTypeParameterMap(currentType, null);
for (Type genericInterfaceType : currentClass.getGenericInterfaces())
{
List<Class> result = getGenericInterfaceTypeArguments(baseInterface, (ParameterizedType)genericInterfaceType, typeVarMap);
if (result != null)
{
return result;
}
}
return null;
}
private static Map<String, Class> createTypeParameterMap(ParameterizedType type, Map<String, Class> extendedTypeMap)
{
Map<String, Class> typeVarMap = new HashMap<String, Class>();
Type[] typeArgs = type.getActualTypeArguments();
TypeVariable[] typeVars = getClass(type).getTypeParameters();
for (int typeArgIndex = 0; typeArgIndex < typeArgs.length; ++typeArgIndex)
{
// Does not deal with nested generic arguments...
Type typeArg = typeArgs[typeArgIndex];
if (typeArg instanceof TypeVariable)
{
assert extendedTypeMap != null;
TypeVariable typeVar = (TypeVariable)typeArg;
typeVarMap.put(typeVars[typeArgIndex].getName(), extendedTypeMap.get(typeVar.getName()));
continue;
}
typeVarMap.put(typeVars[typeArgIndex].getName(), getClass(typeArgs[typeArgIndex]));
}
return typeVarMap;
}
private static List<Class> createTypeParameterList(Map<String, Class> typeParameterMap, ParameterizedType type)
{
ArrayList<Class> typeParameters = new ArrayList<Class>(typeParameterMap.size());
for (Type actualType : type.getActualTypeArguments())
{
if (actualType instanceof TypeVariable)
{
// Handles the case when an interface is created with a specific type, rather than a parameter
typeParameters.add(typeParameterMap.get(((TypeVariable)actualType).getName()));
continue;
}
typeParameters.add(getClass(actualType));
}
return typeParameters;
}
@SuppressWarnings("unchecked")
private static List<Class> getGenericInterfaceTypeArguments(Class baseInterface, ParameterizedType currentType, Map<String, Class> currentTypeParameters)
{
Class currentClass = getClass(currentType);
if (!baseInterface.isAssignableFrom(currentClass))
{
// Early out - current type is not an interface that extends baseInterface
return null;
}
if (currentClass == baseInterface)
{
return createTypeParameterList(currentTypeParameters, currentType);
}
currentTypeParameters = createTypeParameterMap(currentType, currentTypeParameters);
for (Type genericInterface : currentClass.getGenericInterfaces())
{
List<Class> result = getGenericInterfaceTypeArguments(baseInterface, (ParameterizedType)genericInterface, currentTypeParameters);
if (result != null)
{
return result;
}
}
return null;
}
}
Ответ 9
Apache Commons имеет утилиту для выполнения всего этого...
http://commons.apache.org/lang/api/org/apache/commons/lang3/reflect/TypeUtils.html
Посмотрите, как он используется на странице github: https://github.com/apache/commons-lang/blob/72be39f4facb4a5758b9f646309328b764216da3/src/test/java/org/apache/commons/lang3/reflect/TypeUtilsTest.java