Почему бы не удалить стирание стилей из следующей JVM?
Java представил стирание стилей с generics в Java 5, чтобы они работали на старых версиях Java. Это была комбинация совместимости. С тех пор мы потеряли эту совместимость [1] [2] [3] --bytecode, которая может быть запущена на более поздних версиях JVM, но не ранее. Это выглядит как худший вариант: мы потеряли информацию о типе, и мы по-прежнему не можем запустить байт-код, скомпилированный для более ранних версий JVM в старых версиях. Что случилось?
В частности, я спрашиваю, есть ли какие-либо технические причины, по которым стирание стилей невозможно удалить в следующей версии JVM (предполагая, что, как и предыдущие выпуски, его байт-код в любом случае не сможет работать в последней версии).
[3]: Стирание типа может быть обращено таким же образом, как retrolambda для тех, кому это действительно нравится.
Редактирование: Я думаю, что обсуждение определения обратной и обратной совместимости заслоняет вопрос.
Ответы
Ответ 1
В некоторой степени стирание будет удалено в будущем с помощью проекта valhalla, чтобы включить специализированные реализации для типов значений.
Или, если это более точно, тип erasure действительно означает отсутствие специализации типа для дженериков, а valhalla будет вводить специализацию по примитивам.
В частности, я спрашиваю, есть ли какие-либо технические причины, по которым стирание стилей невозможно удалить в следующей версии JVM
Представление. Вам не нужно создавать специализированный код для всех комбинаций родовых типов, экземпляров или сгенерированных классов, которые не должны переносить теги типа, полиморфные встроенные кэши и проверки типа времени выполнения (instanceof
созданные с помощью компилятора), остаются простыми, и мы по-прежнему получаем большинство безопасности типа путем проверки времени компиляции.
Конечно, есть также много недостатков, но компромисс уже сделан, и вопрос, что бы побудило разработчиков JVM изменить этот компромисс.
И это также может быть совместимость, может быть код, который выполняет неконтролируемые отбрасывания, чтобы злоупотреблять родовыми коллекциями, полагаясь на стирание типа, которое сломалось бы, если бы были применены ограничения типа.
Ответ 2
Стирание типа - это больше, чем просто функция байтового кода, которую вы можете включить или отключить.
Это влияет на работу всей среды выполнения. Если вы хотите иметь возможность запрашивать общий тип каждого экземпляра универсального класса, это означает, что метаинформация, сопоставимая с представлением Class
времени выполнения, создается для каждого экземпляра объекта универсального класса.
Если вы пишете new ArrayList<String>(); new ArrayList<Number>(); new ArrayList<Object>()
new ArrayList<String>(); new ArrayList<Number>(); new ArrayList<Object>()
new ArrayList<String>(); new ArrayList<Number>(); new ArrayList<Object>()
вы не только создаете три объекта, вы потенциально создаете три дополнительных метаобъекта, отражающих типы: ArrayList<String>
, ArrayList<Number>
и ArrayList<Object>
, если они раньше не существовали.
Подумайте, что в типичном приложении используется тысяча различных подписей к List
, большинство из которых никогда не использовались в месте, где требуется наличие такого отражения (из-за отсутствия этой функции мы могли бы заключить, что в настоящее время все они работа без такого отражения).
Это, разумеется, размножается, тысячи разных типов списков типов подразумевают тысячи разных типов итераторов, тысячи spliterator и Stream incarnations, даже не считая внутренних классов реализации.
И это даже влияет на места без выделения объектов, которые в настоящее время исследуют стирание типа под капотом, например Collections.emptyList()
, Function.identity()
или Comparator.naturalOrder()
и т.д. Возвращают один и тот же экземпляр при каждом вызове, Если вы настаиваете на том, чтобы групповой захват генерируемого типа отражался, он больше не работает. Поэтому, если вы пишете
List<String> list=Collections.emptyList();
List<Number> list=Collections.emptyList();
вам нужно будет получить два разных экземпляра, каждый из которых сообщает о другом getClass()
или будущем эквиваленте.
Кажется, люди, желающие этой способности, имеют узкий взгляд на свой особый метод, где было бы здорово, если бы они могли рефлексивно выяснить, является ли один конкретный параметр на самом деле одним из двух или трех типов, но никогда не думайте о весе переноса метаинформацию о потенциально сотни или тысячи генерирующих экземпляров тысяч родовых классов.
Это то место, где мы должны спросить, что мы получаем взамен: способность поддерживать сомнительный стиль кодирования (это то, что влияет на поведение кодов из-за информации, найденной через Reflection).
Ответ до сих пор касался только простого аспекта удаления стирания стилей, стремления к интроспекции типа фактического экземпляра. Фактический экземпляр имеет конкретный тип, о котором можно сообщить. Как упоминалось в этом комментарии от пользователя the8472, требование удаления стирания типа часто также подразумевает желание использовать (T)
или создавать массив через new T[]
или получить доступ к типу переменной типа через T.class
.
Это поднимет настоящий кошмар. Переменная типа - это другой зверь, чем фактический тип конкретного экземпляра. Переменная типа может быть разрешена, например ? extends Comparator<? super Number>
? extends Comparator<? super Number>
? extends Comparator<? super Number>
чтобы назвать один (довольно простой) пример. Предоставление необходимой метаинформации подразумевает, что не только распределение объектов становится намного дороже, каждый вызов метода может налагать эти дополнительные затраты еще шире, так как теперь мы не только говорим о комбинации родовых классов с фактическими классами, но и также всевозможные подстановочные комбинации, даже вложенные родовые типы.
Имейте в виду, что фактический тип параметра типа может также ссылаться на другие параметры типа, превращая проверку типов в очень сложный процесс, который вам не только нужно повторять для каждого типа, если вы разрешаете создавать массив из он, каждая операция хранения должна повторить его.
Помимо большой проблемы с производительностью, сложность вызывает еще одну проблему. Если вы посмотрите список отслеживания ошибок javac
или связанные с ним вопросы Stackoverflow, вы можете заметить, что этот процесс не только сложный, но и подверженный ошибкам. В настоящее время каждая младшая версия javac
содержит изменения и исправления в отношении соответствия подписи общего типа, влияющие на то, что будет принято или отклонено. Я уверен, что вам не нужны внутренние операции JVM, такие как приведение типов, назначения переменных или массивы массивов, чтобы стать жертвой этой сложности, имея другое представление о том, что является законным или нет в каждой версии, или внезапно отвергает то, что javac
приняло во время компиляции к несоответствующим правилам.
Ответ 3
Ваше понимание обратной совместимости неверно.
Желаемая цель состоит в том, чтобы новая JVM могла корректно и без изменений запускать старый код библиотеки даже с новым кодом. Это позволяет пользователям надежно обновлять свои версии Java даже в гораздо более новых версиях, чем для этого кода.
Ответ 4
В частности, я спрашиваю, есть ли какие-либо технические причины, по которым стирание стилей невозможно удалить в следующей версии JVM (предполагая, что, как и предыдущие выпуски, его байт-код в любом случае не сможет работать в последней версии).
Потому что это, скорее всего, уничтожит обратную совместимость. (Реальная обратная совместимость... не мифический вид, о котором говорил ваш оригинальный вопрос.)
Да, это техническая причина, потому что обратная совместимость является одним из важнейших свойств языка Java/платформы. По крайней мере, это перспектива Oracle и Oracle, платящих клиентов.
Если стирание типа перестает работать, неожиданно сотни миллиардов долларов инвестиций в Java-программное обеспечение, созданное многими, многими предприятиями в течение последних 20 лет, подвергаются риску.
Спросите себя об этом. Почему Sun/Oracle не удалили Thread.stop()
?