Java Generics: доступ к универсальному типу во время выполнения

Я ищу доступ к родовому типу объявленного поля во время выполнения. Раньше у меня создалось впечатление, что это невозможно из-за стирания Java-типа. Однако это не должно быть так, потому что некоторые хорошо известные фреймворки используют общий тип посредством отражения во время выполнения.

В качестве примера, Guice будет внедрять поставщика на основе общего типа, который вы предоставляете:

public class Injectable{

    @Inject
    private Provider<SomeType> someTypeProvider;

}

Как получить доступ к универсальному атрибуту "SomeType" для поля или любого такого типа/метода/etc через API отражения?

Кроме того, было бы полезно также знать, как получить доступ к этим атрибутам типового типа с помощью API-интерфейсов Java 6 Annotation Processor.

Спасибо.

Edit:

Спасибо всем за ваши большие указатели. Я нашел способ сделать это, используя ссылки haylem, в частности, статью Пренкова Java Reflection: Generics.

Вот ответ, который я искал:

/**
 * @author John Ericksen
 */
public class TypeReflectionExample {

    public class SomeType{}

    public class Injectable{
        @Inject  private Provider<SomeType> someTypeProvider;
    }

    public static void main(String[] args){

        try {
            Field providerField = Injectable.class.getDeclaredField("someTypeProvider");

            Type genericFieldType = providerField.getGenericType();

            if(genericFieldType instanceof ParameterizedType){
                ParameterizedType aType = (ParameterizedType) genericFieldType;
                Type[] fieldArgTypes = aType.getActualTypeArguments();
                for(Type fieldArgType : fieldArgTypes){
                    Class fieldArgClass = (Class) fieldArgType;
                    System.out.println("fieldArgClass = " + fieldArgClass);
                }
            }
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }   
}

приводит к:

fieldArgclass= класс test.TypeReflectionExample $SomeType

То же самое можно сделать для методов, конструкторов, расширений/инструментов Суперкласса и т.д.

Я награждаю haylem, так как его сообщение привело меня к этому решению, даже если оно прямо не ответило на мой вопрос.

Ответы

Ответ 1

Верно, что generics обычно не известны во время выполнения на Java, потому что они реализованы с помощью типа Erasure.

Отражение дженериков?

Однако вы можете извлечь важную информацию о объявленных типах (НЕ типы объектов времени исполнения), как показано в статье Яна Роберстона Отражение дженериков и статья Пренкова Java Reflection: Generics.

Фон для дженериков и стирания типов

Генерики, которые вводятся при сохранении обратной совместимости на двоичном уровне источника qnd, следовательно, некоторые из их ограничений, например:

  • невозможность иметь короткую форму без хотя бы некоторого индикатора для поддержки дженериков (здесь, так называемый оператор алмаза <>),
  • невозможность проверки типичных типов во время выполнения, поскольку они должны были быть реализованы с помощью Type Erasure.

Дополнительная литература

Ответ 2

Хорошо, почему бы вам посмотреть, что такое guice? Источник доступен для общественности.

Guice делает это на нескольких уровнях. Один из них - это тип литералов.

Ключевым моментом здесь является то, что в то время как типы скомпилированы с использованием стирания типа (поэтому для каждого типа существует только один класс), все еще существуют несколько объектов Type, которые знают используемые дженерики. Однако код оптимизирован независимо от него (поскольку он был скомпилирован для каждого класса, а не для каждого типа).

Посмотрите на API Java ParameterizedType.

Итак, хотя верно, что Java Generics реализованы с помощью стирания типа на уровне класса, это не полностью удерживает уровень Type. К сожалению, типы обработки намного сложнее, чем классы. Кроме того, это также означает, что Java не может оптимизировать дженерики так же, как это делает С++, в частности, для примитивных типов. ArrayList<Integer> будет по дизайну быть объектами, содержащими ArrayList<?>, и, если это возможно, не будет поддерживаться встроенным массивом int[].

Обратите внимание, что это, однако, довольно близко к тому, чтобы отслеживать эти вещи самостоятельно. Скажем, очень наивно (он не будет работать с вложенными генериками), вы можете просто расширить ArrayList<T> классом, который имеет поле Class<T> contentClass, тогда вы сможете узнать эту информацию во время выполнения. (Однако TypeLiteral может быть лучшим выбором вместо класса здесь!) Кроме того, JRE фактически не гарантирует, что список остается согласованным. Точно так же, как вы могли бы нарисовать ArrayList<Integer> в нетипизированном ArrayList и добавить объект String.

Ответ 3

Я использовал это год назад. Это может помочь вам

Type typeOfSrc = type(YourClass.class, clazz);

// type(C, A1,...,An) => C<A1,...,An>
static ParameterizedType type(final Class raw, final Type... args)
{
    return new ParameterizedType()
    {
        public Type getRawType(){ return raw; }

        public Type[] getActualTypeArguments(){ return args; }

        public Type getOwnerType(){ return null; }
    };
}

Это позволяет вам получить доступ к типу Generic во время выполнения. В основном, что вы здесь делаете, это хранение общей информации в другом классе и использование этого класса для извлечения этой информации во время выполнения.

Ответ 4

Я считаю, что guice использует TypeLiteral для инкапсуляции общей информации в отдельный объект.