Какова концепция стирания в 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.
Другие ресурсы:
- Документация Oracle
- Википедия
- Руководство Гилада Брачи по дженерикам Java (PDF - настоятельно рекомендуется; ссылку, возможно, придется периодически менять)
- Часто задаваемые вопросы по дженерикам Java от Анжелики Лангер
Ответ 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();
}