Какова концепция стирания в генерики в Java?
какова концепция стирания в дженериках на Java?
8 ответов
это в основном способ, которым дженерики реализуются на Java через обман компилятора. Скомпилированный универсальный код на самом деле просто использует java.lang.Object
там, где вы говорите о T
(или какой - то другой параметр типа) - и есть некоторые метаданные, чтобы сообщить компилятору, что это действительно универсальный тип.
когда вы компилируете некоторый код против общего типа или метода, компилятор работает над тем, что вы действительно имеете в виду (т. е. какой аргумент типа для T
is) и проверяет на compile время, когда вы делаете правильную вещь, но излучаемый код снова просто говорит с точки зрения java.lang.Object
- компилятор генерирует дополнительные приведения там, где это необходимо. Во время исполнения,List<String>
и List<Date>
точно такие же; дополнительная информация о типе была удалены компилятором.
сравните это с, скажем, C#, где информация сохраняется во время выполнения, позволяя коду содержать такие выражения, как typeof(T)
который является эквивалентно T.class
- за исключением того, что последний является недействительным. (Есть и другие отличия .Продажи дженериков и Java дженериков, заметь.) Стирание типа является источником многих "нечетных" предупреждений/сообщений об ошибках при работе с Java generics.
другие ресурсы:
- документация Oracle
- Википедия
- руководство Java generics Гилада Брачи (PDF - настоятельно рекомендуется; ссылка может периодически меняться)
- Анжелика Лангер Java Generics FAQ
в качестве примечания, это интересное упражнение, чтобы действительно увидеть, что делает компилятор, когда он выполняет стирание - делает всю концепцию немного легче понять. Существует специальный флаг, который вы можете передать компилятору для вывода java-файлов, у которых были стерты дженерики и вставлены приведения. Пример:
javac -XD-printflat -d output_dir SomeFile.java
The -printflat
- Это флаг, который передается компилятору, который генерирует файлы. (The -XD
часть-это то, что говорит javac
в исполняемый файл jar, который фактически выполняет компиляцию, а не просто javac
, но я отвлекся...) The -d output_dir
необходимо, потому что компилятору нужно место для размещения нового .Java-файл.
это, конечно, делает больше, чем просто стирание; все автоматические вещи компилятор делается здесь. Например, также вставляются конструкторы по умолчанию, новый foreach-style for
петли расширяются до регулярных for
петли и т. д. Приятно видеть мелочи, которые происходит автоматически.
чтобы завершить уже очень полный ответ Джона Скита, вы должны реализовать концепцию стирания типа происходит от потребности совместимость с предыдущими версиями Java.
первоначально представленный на EclipseCon 2007 (больше не доступен), совместимость включала эти точки:
- исходная совместимость (приятно иметь...)
- бинарная совместимость (обязательно есть!)
- совместимость миграции
- существующие программы должны продолжать работать
- существующие библиотеки должны иметь возможность использовать универсальные типы
- должно быть!
оригинальный ответ:
отсюда:
new ArrayList<String>() => new ArrayList()
есть предложения для большего овеществления. Reify being "рассматривает абстрактное понятие как реальное", где языковые конструкции должны быть понятия, а не только синтаксический сахар.
Я должен также упомянуть checkCollection
метод Java 6, который возвращает динамически типизированное представление указанной коллекции. Любая попытка вставить элемент неправильного типа приведет к немедленному ClassCastException
.
механизм дженерики в языке обеспечивает проверку типов во время компиляции (статическую), но можно победить этот механизм с непроверенными приведениями.
обычно это не проблема, так как компилятор выдает предупреждения обо всех таких непроверенных операциях.
однако бывают случаи, когда одной статической проверки типа недостаточно, например:
- когда коллекция передается в стороннюю библиотеку, и крайне важно, чтобы код библиотеки не повредил коллекцию, вставив элемент неправильного типа.
- сбой программы с
ClassCastException
, указывая, что неправильно введенный элемент был помещен в параметризованная коллекция. К сожалению, исключение может произойти в любое время после вставки ошибочного элемента, поэтому оно обычно предоставляет мало или вообще никакой информации о реальном источнике проблемы.
обновление июля 2012 года, почти четыре года спустя:
сейчас (2012) вся в "правила совместимости миграции API (тест подписи)"
язык программирования Java реализует дженерики с использованием стирания, что гарантирует, что устаревшие и общие версии обычно генерируют идентичные файлы классов, за исключением некоторой вспомогательной информации о типах. Бинарная совместимость не нарушается, поскольку можно заменить файл устаревшего класса на файл универсального класса без изменения или перекомпиляции кода клиента.
чтобы облегчить взаимодействие с неродовым устаревшим кодом, также можно использовать стирание параметризованного типа в качестве типа. Такой тип называется raw тип (Спецификация Языка Java 3/4.8). Разрешение типа raw также обеспечивает обратную совместимость исходного кода.
в соответствии с этим, следующие версии
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();
}
стирание, буквально означает, что информация о типе, присутствующая в исходном коде, стирается из скомпилированного байт-кода. Давайте разберемся в этом с помощью некоторого кода.
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();
}
}
дополняя уже дополненный ответ Джона Скита...
было упомянуто, что реализация дженериков через стирание приводит к некоторым раздражающим ограничениям (например, нет new T[42]
). Было также упомянуто, что основной причиной для этого была обратная совместимость в байт-коде. Это также (в основном) верно. Сгенерированный байт-код-target 1.5 несколько отличается от просто de-sugared casting-target 1.4. Технически, это возможно (через огромный обман), чтобы получить доступ к экземплярам универсального типа во время, доказывая, что в байт-коде действительно что-то есть.
более интересный момент (который не был поднят) заключается в том, что реализация дженериков с использованием стирания предлагает немного больше гибкости в том, что может выполнить система типов высокого уровня. Хорошим примером этого может быть реализация JVM Scala против CLR. На JVM, возможно снабдить высок-виды сразу должные к дело в том, что сама JVM не накладывает никаких ограничений на родовые типы (поскольку эти "типы" фактически отсутствуют). Это контрастирует с CLR, который имеет знания среды выполнения экземпляров параметров. Из-за этого сама среда CLR должна иметь некоторое представление о том, как следует использовать дженерики, сводя на нет попытки расширить систему с помощью непредвиденных правил. В результате более высокие виды Scala на CLR реализованы с использованием странной формы стирания, эмулированной внутри самого компилятора, что делает их не полностью совместим с обычными генераторами .NET.
стирание может быть неудобно, когда вы хотите озорничать во время выполнения, но он предлагает большую гибкость для разработчиков компилятора. Я предполагаю, что это часть того, почему он не уходит в ближайшее время.
Как я понимаю (будучи .NET парень) то JVM не имеет понятия о дженериках, поэтому компилятор заменяет параметры типа Object и выполняет все приведение для вас.
Это означает, что дженерики Java-это не что иное, как синтаксический сахар и не предлагают никакого улучшения производительности для типов значений, которые требуют бокса/распаковки при передаче по ссылке.
есть хорошие объяснения. Я только добавляю пример, чтобы показать, как стирание типа работает с декомпилятором.
класса,
import java.util.ArrayList;
import java.util.List;
public class S<T> {
T obj;
S(T o) {
obj = o;
}
T getob() {
return obj;
}
public static void main(String args[]) {
List<String> list = new ArrayList<>();
list.add("Hello");
// for-each
for(String s : list) {
String temp = s;
System.out.println(temp);
}
// stream
list.forEach(System.out::println);
}
}
декомпилированный код из байт-кода,
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Objects;
import java.util.function.Consumer;
public class S {
Object obj;
S(Object var1) {
this.obj = var1;
}
Object getob() {
return this.obj;
}
public static void main(String[] var0) {
ArrayList var1 = new ArrayList();
var1.add("Hello");
// for-each
Iterator iterator = var1.iterator();
while (iterator.hasNext()) {
String string;
String string2 = string = (String)iterator.next();
System.out.println(string2);
}
// stream
PrintStream printStream = System.out;
Objects.requireNonNull(printStream);
var1.forEach(printStream::println);
}
}
Generic programming вводится в версии java 1.5
прежде всего, что такое generic в java?
Generic programming-это объект типа safe, перед generic в коллекции мы можем хранить любой тип объекта. и после родовой мы должны хранить данные определенного типа объекта.
что преимущества родового?
Главные преимущества родового типа отливка необходимы и также тип-шалфей и родовое проверят время компиляции. и общий синтаксис Generic-ClassOrInterface здесь тип-это сигнал о том, что этот класс может установить класс при создании экземпляра
пример. GenericClassDemo
genericclassDemo = новый GenericClassDemo (сотрудник.java)