Вопрос-ответ

What is the concept of erasure in generics in Java?

Какова концепция стирания в generics на Java?

Какова концепция стирания в generics на Java?

Переведено автоматически
Ответ 1

По сути, это способ, которым дженерики реализуются в Java с помощью хитрости компилятора. Скомпилированный универсальный код на самом деле просто использует java.lang.Object везде, о чем вы говорите T (или какой-либо другой параметр типа) - и есть некоторые метаданные, которые сообщают компилятору, что это действительно универсальный тип.

Когда вы компилируете некоторый код для универсального типа или метода, компилятор определяет, что вы на самом деле имеете в виду (т. Е. Каков аргумент типа для T) и проверяет во время компиляции, что вы поступаете правильно, но генерируемый код снова просто говорит в терминах java.lang.Object - компилятор генерирует дополнительные приведения там, где это необходимо. Во время выполнения a List<String> и a List<Date> точно такие же; дополнительная информация о типе была удалена компилятором.

Сравните это, скажем, с C #, где информация сохраняется во время выполнения, позволяя коду содержать такие выражения, как typeof(T) что эквивалентно T.class - за исключением того, что последнее недопустимо. (Есть дополнительные различия между .NET generics и Java generics, имейте в виду.) Стирание типов является источником многих "странных" предупреждений / сообщений об ошибках при работе с Java generics.

Другие ресурсы:

Ответ 2

Просто в качестве дополнительного примечания, это интересное упражнение, чтобы на самом деле увидеть, что делает компилятор, когда он выполняет стирание - это немного упрощает понимание всей концепции. Существует специальный флаг, который вы можете передать компилятору для вывода java-файлов, в которых были удалены дженерики и вставлены приведения. Пример:

javac -XD-printflat -d output_dir SomeFile.java

-printflat - это флаг, который передается компилятору, генерирующему файлы. (-XD Часть - это то, что говорит javac передать это исполняемому jar, который на самом деле выполняет компиляцию, а не просто javac, но я отвлекся ...) -d output_dir Необходимо, потому что компилятору нужно какое-то место для размещения новых файлов .java.

Это, конечно, делает больше, чем просто стирание; все автоматические действия, выполняемые компилятором, выполняются здесь. Например, также вставлены конструкторы по умолчанию, новые for циклы в стиле foreach расширены до обычных for циклов и т.д. Приятно видеть мелочи, которые происходят автоматически.

Ответ 3

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

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class GenericsErasure {
public static void main(String args[]) {
List<String> list = new ArrayList<String>();
list.add("Hello");
Iterator<String> iter = list.iterator();
while(iter.hasNext()) {
String s = iter.next();
System.out.println(s);
}
}
}

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

import java.io.PrintStream;
import java.util.*;

public class GenericsErasure
{

public GenericsErasure()
{
}

public static void main(String args[])
{
List list = new ArrayList();
list.add("Hello");
String s;
for(Iterator iter = list.iterator(); iter.hasNext(); System.out.println(s))
s = (String)iter.next();

}
}
Ответ 4

Чтобы дополнить и без того очень полный ответ Джона Скита, вы должны осознать, что концепция стирания типов вытекает из необходимости совместимости с предыдущими версиями Java.

Первоначально представленная на EclipseCon 2007 (больше недоступна), совместимость включала эти пункты:


  • Совместимость с исходным кодом (приятно иметь ...)

  • Двоичная совместимость (должна быть!)

  • Совместимость с миграцией

    • Существующие программы должны продолжать работать

    • Существующие библиотеки должны уметь использовать универсальные типы

    • Должно быть!


Оригинальный ответ:

Следовательно:

new ArrayList<String>() => new ArrayList()

Существуют предложения для большей овеществления. Значение Reify означает "Рассматривать абстрактное понятие как реальное", где языковые конструкции должны быть концепциями, а не просто синтаксическим сахаром.

Я должен также упомянуть checkCollection метод Java 6, который возвращает динамически типизированное представление указанной коллекции. Любая попытка вставить элемент неправильного типа приведет к немедленному ClassCastException.

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

Обычно это не проблема, поскольку компилятор выдает предупреждения обо всех таких непроверенных операциях.

Однако бывают случаи, когда одной статической проверки типов недостаточно, например:


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

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


Обновление от июля 2012 года, почти четыре года спустя:

Теперь (2012) это подробно описано в "Правилах совместимости миграции API (проверка подписи)"


Язык программирования Java реализует generics с использованием erasure, который гарантирует, что устаревшие и универсальные версии обычно генерируют идентичные файлы классов, за исключением некоторой вспомогательной информации о типах. Двоичная совместимость не нарушается, потому что можно заменить файл устаревшего класса файлом универсального класса без изменения или перекомпиляции какого-либо клиентского кода.


Для облегчения взаимодействия с неродовым унаследованным кодом также возможно использовать стирание параметризованного типа в качестве типа. Такой тип называется необработанным типом (спецификация языка Java 3/4.8). Разрешение необработанного типа также обеспечивает обратную совместимость исходного кода.


В соответствии с этим, следующие версии java.util.Iterator класса обратно совместимы как с двоичным кодом, так и с исходным кодом:


Class java.util.Iterator as it is defined in Java SE version 1.4:

public interface Iterator {
boolean hasNext();
Object next();
void remove();
}

Class java.util.Iterator as it is defined in Java SE version 5.0:

public interface Iterator<E> {
boolean hasNext();
E next();
void remove();
}
java generics