Подтвердить что ты не робот

Извлечение параметров типа из экземпляра базового базового интерфейса

Для двух интерфейсов:

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

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

4b9b3361

Ответ 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;
}

}