После стольких лет использования дженериков Java, что вы знаете об этом?

Java

Для программиста на Java повседневное программирование уже давно неотделимо от дженериков. Универсальные шаблоны действительно повысили производительность с момента их появления в JDK1.5. простой универсальныйT, С помощью всего нескольких строк кода мы можем динамически заменить его любым желаемым типом во время использования, и нам больше не нужно реализовывать громоздкие методы преобразования типов.

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

  • Реализация дженериков Java
  • Подводные камни стирания шрифта
  • История дженериков Java

Ставьте лайк и смотрите снова, формируйте привычку и ищите «Program Communication» в WeChat.Нажмите, чтобы увидеть больше статей по теме

Реализация дженериков Java

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

Для следующего кода после компиляции мы используемjavap -s classПроверьте байт-код.

方法源码

字节码

наблюдатьsetParamчасть байт-кода, изdescriptorКак видите, общийTбыл стерт и в конечном итоге заменен наObject.

ps: не каждый общий параметр станет объектом после удаления его типа.Если универсальный тип T extends String, окончательный универсальный тип станет строкой после удаления.

так жеgetParamметод, общее возвращаемое значение также заменяется наObject.

чтобы убедиться, чтоString param = genericType.getParam();Корректность кода, компилятор должен вставлять здесь преобразования типов.

Кроме того, компилятор также защитит от универсальной безопасности, если мы перейдем кArrayList<String>Добавить кInteger, во время компиляции программы будет сообщено об ошибке.

Код после окончательного стирания типа эквивалентен следующему:

Подводные камни стирания шрифта

Для сравнения краткоC#Общая реализация.

**C#** Generics реализованы как "Повторяемые дженерики", незнакомыйC#Друзья, не волнуйтесьматериализоватьсяТехнические концепции, я тоже не понимаю этих особенностей --!

Проще говоря, дженерики, реализованные **C#**, существуют в исходном коде программы, после компиляции или даже во время выполнения.

по сравнению сC#Дженерики, дженерики Java выглядят как "псевдо"Универсальный. Java-дженерики существуют только в исходном коде программы и стираются после компиляции, соответственно этот дефект принесет некоторые проблемы.

Базовые типы данных не поддерживаются

После того, как общий параметр стерт, приведение становитсяObjectТипы. В конце концов, это нормально для ссылочных типов.Objectявляется супертипом всех типов. Но дляint/longВ ожидании, когда скажут восемь основных типов данных, это сложно сделать. Потому что Java не может этого сделатьint/longа такжеObjectпринудительное преобразование.

Если это преобразование должно быть реализовано, требуется ряд преобразований, и трудность изменений не мала. Так что в то время Java давала простое и грубое решение: раз нет возможности конвертировать, то она просто не поддерживает дженерики примитивного типа.

Если вам нужно его использовать, укажите использование универсального типа соответствующего класса-оболочки, напримерArrayList<Integer>. Кроме того, для удобства разработчиков, кстати, добавлены нативные типы данных.Автоматическая распаковка/упаковкахарактеристики.

Именно этот «ленивый» подход делает нас неспособными сейчас использовать дженерики примитивных типов, и нам приходится терпеть накладные расходы, вызванные упаковкой/распаковкой классов упаковки, что приводит к проблемам с эффективностью работы.

операционная эффективность

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

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

В дополнение к этому мы также говорили о примитивных примитивных типах данных выше, и компилятор также должен помочь нам с упаковкой/распаковкой.

Итак, для следующего кода:

List<Integer> list = new ArrayList<Integer>();
list.add(66); // 1
int num = list.get(0); // 2

Для ① компилятору нужно увеличить размер упаковки базового типа. Но для второго шага компилятору сначала нужноObjectбросить наInteger, а затем компилятор должен его распаковать.

После стирания типа приведенный выше код эквивалентен следующему:

List list = new ArrayList();
list.add(Integer.valueOf(66));
int num = ((Integer) list.get(0)).intValue();

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

Невозможно получить общий фактический тип во время выполнения

Поскольку дженерики стираются после компиляции, фактический тип дженериков недоступен для виртуальной машины Java во время выполнения кода.

В следующем коде из исходного кода два списка кажутся коллекциями разных типов, но после общего стирания коллекции становятсяArrayList. такifКод в операторе будет выполнен.

ArrayList<Integer> li = new ArrayList<Integer>();
ArrayList<Float> lf = new ArrayList<Float>();
if (li.getClass() == lf.getClass()) { // 泛型擦除,两个 List 类型是一样的
    System.out.println("6666");
}

Это делает код немного нелогичным, что не очень удобно для новичков.

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

Наконец, давайте возьмем пример, скажем, нам нужно реализовать общийListС методом преобразования в массив мы не можем получить фактический тип универсального типа непосредственно из списка, поэтому нам нужно передать дополнительный тип класса, чтобы указать тип массива:

public static <E> E[] convert(List<E> list, Class<E> componentType) {
    E[] array = (E[]) Array.newInstance(componentType, list.size());
    ....
}

Из приведенного выше примера видно, что Java использует стирание типов для реализации дженериков, что имеет много дефектов. Так почему же Java не использует общую реализацию C#? Или лучший способ сделать это?

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

История дженериков Java

Дженерики Java были впервые представлены в JDK5, но идея дженериков впервые пришла из шаблонов C++. В 1996 году Мартин Одерски (создатель языка Scala) расширил функции дженериков и функционального программирования на основе только что выпущенной Java, сформировав новый язык — «Pizza".

Позже группа разработчиков ядра JavaPizzaЯ был глубоко заинтересован в общем дизайне и работал с Мартином над новым проектом.Generic Java". Цель этого проекта — добавить в Java поддержку дженериков, а не внедрять такие функции, как функциональное программирование. Наконец, поддержка дженериков была официально введена в Java 5.

Общий процесс переноса не пошел в направлении стирания шрифта в начале, тот факт,PizzaСредние дженерики больше похожи наC#Дженерики в .

Однако из-за особенностей самой Java она имеет строгие ограничения, что позволяет МартинуGeneric JavaВ процессе разработки пришлось отказаться от общего дизайна Pizza.

Эта функция заключается в том, что Java должен делатьСтрогая обратная совместимость. То есть файл класса, скомпилированный в JDK1.2, может не только нормально работать в JDK 1.2, но также должен обеспечивать нормальную работу в последующих JDK, таких как JDK12.

Эта функция явно прописана в Спецификации языка Java, что является серьезным обязательством для пользователей Java.

Здесь подчеркивается, что обратная совместимость здесь относится к двоичной совместимости, а не к совместимости исходного кода. Также нет гарантии, что более старшая версия файла Class может работать на более ранней версии JDK.

Сложность сейчас в том, что Java 1.4.2 не поддерживала дженерики, а после Java5 нужно внезапно поддерживать дженерики, а программы, скомпилированные до JDK1.4, могут нормально работать в новой версии, а значит, нет и прежней версии. лимит не может быть увеличен внезапно.

Например:

ArrayList arrayList=new ArrayList();
arrayList.add("6666");
arrayList.add(Integer.valueOf(666));

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

Чтобы гарантировать, что эти старые файлы Clas могут нормально работать после Java5, у дизайнера в основном есть два пути:

  1. Для контейнеров (в основном типов контейнеров), которые должны быть универсальными, некоторые из них остаются неизменными ранее, и параллельно добавляется новый набор универсальных версий.
  2. Непосредственно универсализируйте существующий тип на месте, не добавляя новую универсальную версию существующего типа.

Если бы Java пошла по первому пути, у нас могло бы быть два набора типов коллекций. кArrayListНапример, набор обычныхjava.util.ArrayList, множество возможныхjava.util.generic.ArrayList<T>.

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

Это решение кажется без проблем, на самом деле, С# принимает это решение. Но почему Java не использует эту схему?

Это связано с тем, что в то время C# был выпущен всего два года назад, а исторического кода не так много.Если в старом коде нужно использовать универсальную функцию, его можно очень быстро преобразовать. Но Java отличается.В то время Java была выпущена в течение десяти лет, и многие программы были запущены и развернуты в производственной среде.Не исключено, что существует много исторического кода.

Если этим приложениям необходимо использовать дженерики в новой версии Java, то потребуется много изменений в исходном коде, и такую ​​нагрузку на разработку можно себе представить.

Кроме того, до Java 5 у нас фактически было два набора контейнеров коллекций, один дляVector/Hashtableи другие контейнеры, комплектArrayList/ HashMap. Существование этих двух наборов контейнеров на самом деле вызвало некоторые неудобства, и начинающим Java-разработчикам приходится понимать разницу между ними.

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

Поэтому Java выбирает второй путь, используя стирание типов, нужно только изменить компилятор Javac, не нужно менять байт-код, не нужно менять виртуальную машину, и это также гарантирует, что код без дженериков в предыдущей истории все еще может использоваться в новом JDK.run.

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

На технический долг, оставшийся в прошлом, можно рассчитывать только сейчасValhallaПришел проект. Этот проект стартовал в 2014 году, и изначально планировалось решить различные недостатки существующего языка в JDK10. Но в результате мы также знаем, что сейчас это все JDK14, и выполнено лишь небольшое количество деревянных целей, а основные цели не решены, что показывает сложность этого изменения.

Суммировать

В этой статье мы начнем с базовой реализации дженериков Java, а затем приведем несколько примеров, чтобы вы знали, что в текущей реализации дженериков есть некоторые недостатки.

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

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

Справочный материал

  1. Ууху. Call.com/question/38…
  2. Ууху. Call.com/question/28…
  3. Позже Лу Мэн-group.ITeye.com/group/topic…
  4. Блог Чжао Цзе Что/2010/05/почему…
  5. Блог Чжао Цзе Что/04/2010/почему…
  6. En. Wikipedia.org/wiki/День Джина…
  7. Ууху. Call.com/question/34…
  8. Woohoo. Aretima.com/ScalaBad/Ах…

Напоследок (обратите внимание, просите лайки, просите переадресацию)

Эта статья написана только после прочтения «Подробных сведений о виртуальной машине Java (третье издание)» и знакомства с историями о дженериках Java.

Прежде всего, я хотел бы поблагодарить младшего брата из Machinery Industry Press за пожертвование книги.

Когда я впервые узнал, что вышла «Углубленная виртуальная машина Java (третье издание)», я подумал, что это просто небольшое дополнение ко второму изданию. Когда я получил эту книгу, я понял, что ошибался. Если сложить две книги вместе, то получится, что они совсем не одного порядка.

пс: украсть картинку Почему Бог

Третья книга добавляет множество дополнений на основе второго издания, а также решает некоторые необъясненные проблемы, оставленные вторым изданием. Поэтому студентам, не купившим его, рекомендуется приобрести третье издание напрямую.

Чтобы узнать о конкретных различиях между двумя версиями, вы можете прочитать статью «Почему Бог», которая также была вознаграждена автором этой книги.

Углубленное сравнение двух версий виртуальной машины Java

Я младший черный брат внизу, программист, который еще не лысый, увидимся в следующую среду~

Добро пожаловать, чтобы обратить внимание на мой официальный аккаунт: программа для общения, ежедневный толчок галантерейных товаров. Если вас интересует мой рекомендуемый контент, вы также можете подписаться на мой блог:studyidea.cn

公号底图