Получение параметров типа из экземпляра базового интерфейса - PullRequest
20 голосов
/ 17 февраля 2009

Дано 2 интерфейса:

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()

но я не могу найти простой способ перейти на любой базовый универсальный интерфейс и получить что-нибудь осмысленное).

Ответы [ 9 ]

20 голосов
/ 21 февраля 2009

Эту проблему нелегко полностью решить вообще. Например, вам также нужно учитывать параметры типа содержащего класса, если это внутренний класс, ...

Поскольку размышление над универсальными типами очень сложно, используя только то, что предоставляет сама 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);
}
6 голосов
/ 17 февраля 2009

Я не знаю, что именно вы пытаетесь достичь, а что известно, а что нет, но вы можете вернуться к суперинтерфейсу следующим образом:

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]) {
        // ...
    }
}
4 голосов
/ 11 октября 2013

Это трудно решить с помощью Java Reflection API, потому что нужно разрешить все встречающиеся переменные типа. Guava начиная с версии 12 имеет класс TypeToken , который содержит полностью разрешенную информацию о типе.

Для вашего примера вы можете сделать:

TypeToken<? extends T> token = TypeToken.of(MyClass.class);
ParameterizedType type =
    (ParameterizedType) token.getSupertype(BaseInterface.class).getType();
Type[] parameters = type.getActualTypeArguments();

Тем не менее, вы должны помнить, что это работает только в тех случаях, когда MyClass сам по себе не является универсальным. В противном случае значение параметров типа недоступно во время выполнения из-за стирания типа.

1 голос
/ 18 февраля 2009

Это вроде делает то, что вы ищете, но это все еще не правильно. Например, он не обрабатывает случай, когда Foo<T> implements Bar<Map<T>>. Что вам действительно нужно, так это каким-то образом спросить jvm: «Хорошо, вот список типов. Какой фактический тип я получу, если применил их к этому универсальному типу?»

Но этот код делает то, что вам нужно.

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();
    }

}
1 голос
/ 17 февраля 2009

Я не думаю, что есть прямой способ получения универсального типа базового интерфейса.

Один из способов - объявить метод в интерфейсе следующим образом:

public interface BaseInterface<T> {
    Class<T> getGenericClass();
}

Кроме того, я не знаю, какой у вас контроль над этими классами. Вы всегда можете утверждать, что все разработчики имеют базовый интерфейс, явно объявленный как:

public class MyClass implements ExtendedInterface<String, Object>, BaseInterface<String>{ }

и

MyClass.class.getGenericInterfaces()[1].getActualTypeArguments()[0]
0 голосов
/ 15 января 2013

В 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

0 голосов
/ 18 февраля 2009

плохой этикет снова от меня, отвечая на мой собственный вопрос.

Как указал Гикс, в тот момент, когда вы начинаете подниматься по иерархии универсальных типов, за исключением первого, вы теряете информацию об аргументах типов.

Но важными битами являются: вы получаете аргументы типа первого универсального интерфейса, который должен быть инстанцирован (в моем примере, 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;
}

}

0 голосов
/ 17 февраля 2009

Я думаю о единственной возможности, которую я могу придумать, - это проверить общий метод, который объявлен BaseInterface, а не переопределен.

0 голосов
/ 17 февраля 2009

Я не думаю, что вы можете, так как они действительно специфичны для конкретного экземпляра, а не для конкретного класса. Учтите следующее:

List<String>  a = new ArrayList<String>();

Тот факт, что a является общим списком строк, относится к экземпляру a, а не к классу List. Таким образом, ни один из методов объекта List.class не может сказать вам, что обобщенный тип будет иметь тип String для a. Хотя в вашем примере в MyClass установлены значения для обобщенных типов интерфейса, я не думаю, что это будет доступно в экземпляре объекта класса Interface.

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