Стирает ли стирание Java-типа мой общий тип?

Я думал, что стирание java стирает общие типы во время компиляции, однако, когда я сам тестирую его, я понял, что в Bytecode есть некоторая информация об общих типах.

вот мой тест:

Я написал 2 класса:

import java.util.*;
public class Test {
    List integerList;
} 

и

import java.util.*;
public class Test {
    List<Integer> integerList;
} 

i скомпилировал оба класса и где-то в общем классе я видел эту строку

integerList{blah blah}Ljava/util/List;{blah blah}
Signature{blah blah}%Ljava/util/List<Ljava/lang/Integer;>;{blah blah}<init>

в не общем классе:

integerList{blah blah}Ljava/util/List;{blah blah}<init>

так что, очевидно, у меня есть общая информация внутри байт-кода, так что это за стирание?

Ответ 1

Некоторая информация общего типа хранится в атрибутах Signature. Обратитесь JLS 4.8 и 4.6 и спецификация JVM 4.3. 4. Прочитайте здесь:

Вероятно, наиболее распространенная жалоба на дженерики в Java заключается в том, что они не подтверждены - во время выполнения нет способа узнать, что List<String> отличается от List<Long>. Я так привык к этому, что был очень удивлен, когда я столкнулся с работой Нейла Гафтера над токенами Super Type. Оказывается, что, хотя JVM не будет отслеживать фактические аргументы типа для экземпляров универсального класса, он отслеживает фактические аргументы типа для подклассов общих классов. Другими словами, в то время как новый ArrayList<String>() на самом деле просто новый ArrayList() во время выполнения, если класс расширяет ArrayList<String>, тогда JVM знает, что String является фактическим аргументом типа для параметра типа List.

и блог Neal Gafter.

Ответ 2

что это за стирание?

Erasure - это отображение от общего к необработанным типам. Общая фраза "из-за стирания" по существу бессмысленна. Существенными являются спецификации, которые используют отображение.

Существует два интересных примера.

  • Он использовал для сопоставления сигнатур методов с использованием дженериков для необработанных типов. Это сигнатуры raw-type, которые используются для перегрузки. Это приводит к подавляющему большинству проблем с "стиранием". Например, вы не можете использовать два метода add(List<String>) и add(List<Integer>) того же типа. Перегрузка, вероятно, не является отличной идеей, и нет большой готовности добавить эту функцию.

  • Тип, доступный для экземпляра объекта во время выполнения, - это тип, который был создан с удалением. Поэтому, если вы нажмете, скажем, (String), который будет проверен во время выполнения, но если вы нажмете на List<String>, будет проверено только стирание этого типа (List). Вы можете иметь переменные типа List<String> и List<Integer>, указывающие на один и тот же экземпляр. На практике вы не должны использовать броски (ссылочных типов) в 1.5 и более поздних версиях.

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

Ответ 3

Это один из примеров, когда действительно важно точное использование терминологии: Bytecode - это набор команд виртуальной машины Java. Файл класса содержит байт-код, а также информацию, используемую для связывания (сигнатуры полей, сигнатуры методов,...), для верификатора байт-кода, для отладчика,...

Тип стирания означает, что информация общего типа не преобразуется в байтовый код; более конкретно, все экземпляры родового типа имеют одинаковое представление в байтовом коде. Аналогично, динамический тип объекта, за которым отслеживается время выполнения (как используется операторами cast и instanceof и доступным через getClass()), одинаковый для всех экземпляров родового класса независимо от любых параметров типа, поставляемых в источнике код.

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

То есть, начиная с Java 1.5, мы должны различать объявленный тип переменной и тип времени выполнения объекта, к которому он относится. Первый поддерживает дженерики, а второй - нет. И да, это означает, что между временем компиляции и типами времени выполнения нет взаимно однозначного соответствия.

Ответ 4

Информация о типе будет стерта отсюда

 integerList = new ArrayList<Integer>(); 

в байт-коде будет эквивалентно

integerList = new ArrayList(); 

и нет возможности узнать во время выполнения из объекта integerList, каков был его тип времени компиляции.

Ответ 5

Erasure означает, что типичная типизация не включена в код байта (когда список создан или используется).

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