Статьи

Об обобщении и стирании Java

«Обобщение стирается во время компиляции» — это общеизвестно (ну, в общем, параметры типа и аргументы стираются). Это происходит из-за «стирания типа» . Но неправильно, что все, что указано внутри символов <..> стерто, как полагают многие разработчики. Смотрите код ниже:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class ClassTest {
  public static void main(String[] args) throws Exception {
    ParameterizedType type = (ParameterizedType)
       Bar.class.getGenericSuperclass();
    System.out.println(type.getActualTypeArguments()[0]);
     
    ParameterizedType fieldType = (ParameterizedType)
        Foo.class.getField("children").getGenericType();
    System.out.println(fieldType.getActualTypeArguments()[0]);
     
    ParameterizedType paramType = (ParameterizedType)
        Foo.class.getMethod("foo", List.class)
        .getGenericParameterTypes()[0];
    System.out.println(paramType.getActualTypeArguments()[0]);
     
    System.out.println(Foo.class.getTypeParameters()[0]
        .getBounds()[0]);
  }
   
  class Foo<E extends CharSequence> {
    public List<Bar> children = new ArrayList<Bar>();
    public List<StringBuilder> foo(List<String> foo) {return null; }
    public void bar(List<? extends String> param) {}
  }
    
  class Bar extends Foo<String> {}
}

Вы знаете, что это печатает?

класс java.lang.String
class ClassTest $ Bar
класс java.lang.String
класс java.lang.StringBuilder
интерфейс java.lang.CharSequence

Вы видите, что каждый отдельный аргумент типа сохраняется и доступен через отражение во время выполнения. Но тогда что такое «стирание типа»? Что-то должно быть стерто? Да. Фактически, все они, кроме структурных, все вышеизложенное связано со структурой классов, а не с потоком программы. Другими словами, метаданные об аргументах типа класса, его поле и методах сохраняются для доступа через отражение.

Остальное, однако, стирается. Например, следующий код:

1
2
3
4
5
List<String> list = new ArrayList<>();
Iterator<String> it = list.iterator();
while (it.hasNext()) {
   String s = it.next();
}

будет фактически преобразовано в это (байт-код двух фрагментов идентичен):

1
2
3
4
5
List list = new ArrayList();
Iterator it = list.iterator();
while (it.hasNext()) {
   String s = (String) it.next();
}

Таким образом, все аргументы типа, которые вы определили в теле ваших методов, будут удалены, и при необходимости будут добавлены приведения. Кроме того, если определен метод для приема List<T> , этот T будет преобразован в Object (или в его границу, если таковое объявлено. И вот почему вы не можете сделать new T() (кстати, один открытый вопрос об этом стирании).

Итак, мы рассмотрели первые два пункта определения типа стирания . Третий — о мостовых методах. И я проиллюстрировал это с помощью этого вопроса stackoverflow (и ответ) .

Две «морали» всего этого. Во-первых, Java-дженерики сложны. Но вы можете использовать их, не понимая всех сложностей.

Во-вторых, не думайте, что вся информация о типах стерта — аргументы структурного типа присутствуют, поэтому используйте их, если необходимо (но не слишком полагайтесь на рефлексию).

Ссылка: Об Java Generics и Erasure от нашего партнера JCG Божидара Божанова в техническом блоге Божо .