Java Generics: сохраняется ли какая-либо метаинформация об универсальном типе во время выполнения?

Фон

Мое понимание Java generics заключается в том, что это полностью функция времени компиляции (в основном сосредоточенная на проверках безопасности типов). Информация о типе для любого универсального класса теряется во время выполнения (удаление типа).

Тем не менее, я вижу, что многие фреймворки кажутся также используют информацию о типах во время выполнения. Например, Google Guice Поставщики. Поставщик guice может создавать и предоставлять новые экземпляры своего универсального типа во время выполнения.

class Container
{
     @Inject   
     public Container(Provider<Content> contentProvider)
     {
     //This works at Runtime... but How ??? 
     //When type's are not even preserved at runtime, how does the Provider knows it has to instantiate an object of type 'Content' 
     Content content = contentProvider.get();   
     }
}

Вопрос

  1. Есть ли какая-либо информация, связанная с универсальными типами, которая также сохраняется во время выполнения. ? Если да, что? Если нет, то как такие библиотеки, как google guice, работают внутри (пример выше)

  2. Есть ли что-то большее в дженериках, чем просто безопасность во время компиляции? Например, есть ли какой-либо вариант использования (кроме обеспечения безопасности времени компиляции), где можно получить преимущество с помощью дженериков?


person Harshit    schedule 21.08.2017    source источник
comment
Есть ли что-то большее в дженериках, чем просто безопасность времени компиляции?   -  person Andy Turner    schedule 21.08.2017
comment
Обобщения функциональны только как функция времени компиляции, но большая часть общей информации по-прежнему доступна во время выполнения посредством отражения.   -  person shmosel    schedule 21.08.2017
comment
Даунвотер хочет прокомментировать?   -  person Harshit    schedule 22.08.2017


Ответы (6)


Конечно, информация о том, что класс является универсальным, поддерживается.

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

Но когда у вас есть другой класс, который использует некоторый объект List<Integer>, вы не найдете информацию о том, что «список использует целое число» в скомпилированном классе, если вы не используете некоторые определенные шаблоны, как указано здесь, например.

Таким образом, ответ в основном таков: почти во всех случаях использования, имеющих практическое значение, "дженерики" используются только во время компиляции.

Пример:

public class GenericsExample<T> {
  private T member;   
  public T foo(T bar) {
     return member;
  }
}

Теперь запустите: javap -p -c GenericsExample

Compiled from "GenericsExample.java"
public class GenericsExample<T> {
  private T member;

  public GenericsExample();
    Code:
       0: aload_0       
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return        

  public T foo(T);
    Code:
       0: aload_0       
       1: getfield      #2                  // Field member:Ljava/lang/Object;
       4: areturn       
}

Как видите, декомпилятор понимает, что класс использует этот общий тип T. Подробнее см. здесь или здесь.

person GhostCat    schedule 21.08.2017
comment
Любые комментарии от downvoter? OP спрашивает, являются ли дженерики только временем компиляции, и я утверждаю, что META-информация о дженериках является частью файла класса, и, таким образом, к ней можно получить доступ и во время выполнения. - person GhostCat; 21.08.2017
comment
Не голосующий, но вы можете получить конкретную общую информацию из полей и параметров. - person shmosel; 21.08.2017
comment
@shmosel И вы правы - соответственно обновил мой ответ. - person GhostCat; 21.08.2017
comment
Я думаю, что какой-то угрюмый прошел и проголосовал за все в этом вопросе. - person yshavit; 21.08.2017
comment
Не могли бы вы проиллюстрировать на примере. - person Harshit; 24.08.2017
comment
@Harshit Смотрите мои обновления. И, пожалуйста, не забывайте о принятии одного из ответов в какой-то момент. - person GhostCat; 24.08.2017

Если класс расширяет общий класс или интерфейс и предоставляет конкретный тип для параметра, то этот тип доступен через Class.getGenericSuperclass(). Этот метод (в данном случае) вернет ParameterizedType, который будет содержать фактическую параметризацию.

Например, если у вас есть:

class BigIntegerList extends ArrayList<BigInteger> {}

Затем вы можете сделать:

Class<BigIntegerList> fooClass = BigIntegerList.class;
Type superclass = fooClass.getGenericSuperclass();
if (superclass instanceof ParameterizedType) {
  ParameterizedType parameterized = (ParameterizedType) superclass;
  Type[] parameterizations = parameterized.getActualTypeArguments();
  System.out.println(Arrays.toString(parameterizations));
  // prints: "[class java.math.BigInteger]"
}

Это действительно используется библиотеками с большим количеством отражений, такими как Guice. Другим примером является TypeReference, который позволяет вам читать список вещей в формате JSON как list-of-BigDecimal (например).

person yshavit    schedule 21.08.2017
comment
Интересно. Будет копать немного больше об этом. Спасибо - person Harshit; 21.08.2017
comment
Вы, кажется, берете пример, демонстрирующий наследование. (А расширяет В). Можно ли как-то получить доступ к экземпляру ArrayList‹BigInteger› и узнать тип («BigInteger» в упомянутом вами случае). - person Harshit; 24.08.2017
comment
@Harshit Это что-то вроде List<BigInteger> myBigInts = new ArrayList<BigInteger>()? В таком случае ответ очень даже нет. Это стирание для тебя. - person yshavit; 24.08.2017

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

person KLourie    schedule 21.08.2017

Есть ли какая-либо информация, связанная с универсальными типами, которая также сохраняется во время выполнения. ? Если да, то что?.

Единая информация, сохраняемая в скомпилированном классе, представляет собой преобразование необработанных объектов/переменных, полученных после стирания, в определенные типы, используемые в качестве универсальных в исходном коде.
Но они основаны только на объявленном типе переменной, а не на реальном используемом универсальном типе. .

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

Если нет, то как библиотеки, такие как google guice, работают внутри (

Вы ошибаетесь.
В Guice этот код:

 Content content = contentProvider.get();   

вернет экземпляр Content, а не общий тип. Посмотрите документацию. :

Получить()

Предоставляет экземпляр T.

person davidxxx    schedule 21.08.2017
comment
вернет экземпляр Content, а не общий тип. Посмотрите документацию:. Это то, что даже я пытаюсь сказать. Контент был типом здесь (в Provider‹Content›). Поскольку его тип был бы стерт, как провайдер может создать экземпляр «контента» во время выполнения? - person Harshit; 21.08.2017
comment
Ах хорошо. Я понимаю вашу проблему. Когда реализован Provider<T>, он должен предоставить универсальный тип, например Provider<Content>. Итак, чтобы компилироваться нормально, он должен реализовать get() следующим образом: Provider get(). Итак, метод реализован в скомпилированном классе. Он не беспокоится о стирании, как это делалось раньше. - person davidxxx; 21.08.2017

  1. Java Generics использует то, что называется стиранием типа, поэтому во время выполнения информация о типе недоступна. Однако можно создать экземпляр любого класса с помощью метода Class.newInstance(), если информация о типе может быть передана каким-либо образом (фактически, это единственный способ создать универсальный массив).

  2. Безопасность времени компиляции является основной целью дженериков. Но их часто можно использовать и для написания более лаконичного кода, что иначе было бы невозможно.

Для подробного рассмотрения рекомендую прекрасную книгу Java Generics and Collections< /сильный>

person senseiwu    schedule 21.08.2017

Общая информация сохраняется только до времени компиляции. В вашем примере она также доступна до времени компиляции. Поясню на одном примере

Public void printObject(список пуст) {

// Это не показывает, что список хранит эту информацию во время выполнения, какой тип объекта он будет возвращать. Занятость en =empt.get(); }

person sunil    schedule 21.08.2017