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

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

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

Когда вы компилируете какой-либо код против общего типа или метода, компилятор разрабатывает то, что вы действительно имеете в виду (то есть, что такое аргумент типа для T ), и во время компиляции проверяет, что вы поступаете правильно, но испускаемый код снова просто переговоры с точки зрения java.lang.Object – компилятор при необходимости создает дополнительные приведения. Во время выполнения List и List являются точно такими же; дополнительная информация о типе была удалена компилятором.

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

Другие источники:

  • Документация Oracle
  • Википедия
  • Gilad Bracha Java generics guide (PDF – настоятельно рекомендуется, ссылка может потребоваться периодически менять)
  • Часто задаваемые вопросы Java Generics от Angelika Langer

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

 javac -XD-printflat -d output_dir SomeFile.java 

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

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

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

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

  • Совместимость источников (Приятно иметь …)
  • Бинарная совместимость (обязательно!)
  • Миграционная совместимость
    • Существующие программы должны продолжать работать
    • Существующие библиотеки должны иметь возможность использовать общие типы
    • Должен иметь!

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

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

 new ArrayList() => new ArrayList() 

Есть предложения для большего овеществления . Reify «Рассматривать абстрактное понятие как реальное», где языковые конструкции должны быть понятиями, а не только синтаксическим сахаром.

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

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

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

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

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

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

Сейчас (2012) подробно описано в « Правилах совместимости API-интерфейсов (тест подписи) »

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

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

В соответствии с этим следующие версии classа 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 { boolean hasNext(); E next(); void remove(); } 

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

 import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class GenericsErasure { public static void main(String args[]) { List list = new ArrayList(); list.add("Hello"); Iterator 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 несколько отличается от просто дезаваренного литья -target 1.4. Технически, даже возможно (через огромные хитрости) получить доступ к экземплярам универсального типа во время выполнения , доказывая, что в бат-коде действительно есть что-то.

Более интересный момент (который не был поднят) заключается в том, что реализация генерических средств, использующих стирание, предлагает гораздо большую гибкость в том, что может сделать система высокого уровня. Хорошим примером этого может быть реализация JVM Scala и CLR. На JVM можно реализовать более высокие виды напрямую из-за того, что сам JVM не налагает ограничений на общие типы (поскольку эти «типы» фактически отсутствуют). Это контрастирует с CLR, который имеет знание времени исполнения параметров. Из-за этого сам CLR должен иметь некоторую концепцию использования дженериков, сводя на нет попытки расширить систему с непредвиденными правилами. В результате, более высокие виды Scala на CLR реализованы с использованием странной формы стирания, эмулируемой в самом компиляторе, что делает их не полностью совместимыми с обычными генерирующими .NET.

Erasure может быть неудобным, когда вы хотите совершать непослушные вещи во время выполнения, но он обеспечивает максимальную гибкость для авторов компилятора. Я предполагаю, что это часть того, почему он не уйдет в ближайшее время.

Насколько я понимаю (будучи человеком .NET ), JVM не имеет понятия дженериков, поэтому компилятор заменяет параметры типа Object и выполняет все кастинг для вас.

Это означает, что дженерики Java – это не что иное, как синтаксический сахар, и не предлагают какого-либо повышения производительности для типов значений, которые требуют бокса / распаковки при передаче по ссылке.

Есть хорошие объяснения. Я добавляю только пример, показывающий, как стирание типа работает с декомпилятором.

Оригинальный class,

 import java.util.ArrayList; import java.util.List; public class S { T obj; S(T o) { obj = o; } T getob() { return obj; } public static void main(String args[]) { List 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); } } 

В java 1.5 версии вводится общее программирование
Прежде всего, что является общим в java?
Общее программирование – это безопасный объект типа. Перед общим набором в коллекции мы можем хранить любой тип объекта. и после генерации мы должны хранить данные определенного типа объекта.

В чем преимущества Generic?
Основными преимуществами родового является то, что литье по типу не требуется, а также тип-мудрец и Generic будут проверять время компиляции. и общий синтаксис Generic – это ClassOrInterface здесь type – это сигнал, который этот class мог бы установить для classа при его создании

Пример . GenericClassDemo

genericclassDemo = новый GenericClassDemo (Employee.java)

  • Почему java-приложение врезается в gdb, но работает нормально в реальной жизни?
  • Ожидание списка Будущего
  • Добавление аннотаций Java во время выполнения
  • Как сделать форматирование от 1200 до 1.2k в java
  • Где Visual Web Editor для JavaServer Faces на Netbeans
  • Конечные аргументы в методах интерфейса - в чем смысл?
  • Почему filter () после того, как flatMap () «не полностью» ленив в streamах Java?
  • Сглаживание зубчатого пути
  • Почему Hibernate Open Session in View считается плохой практикой?
  • Исключение NoClassDefFoundError для CacheProvider
  • Разбор JSON с GSON, объект иногда содержит список, иногда содержащий объект
  • Давайте будем гением компьютера.