Почему Java утверждает, что версия 1.5 должна быть обратно совместима?

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

например,

class A<T> {
    <U> U myMethod(U u) {
        return u;
    }
}

Если я сделаю ,

A a = new A();

a.myMethod попросит меня передать Object в качестве входных данных, а тип возвращаемого значения — Object. Ясно, что параметр типа класса «T» не конфликтует со специфичным для myMethod() универсальным параметром, который равен «U». Но как-то java сказала, что для обеспечения обратной совместимости они удаляют использование всех дженериков для необработанных экземпляров.

Вышеупомянутый вопрос задают несколько раз другие, и все ответы вращаются, говоря, что из-за обратной совместимости с более ранними версиями java это запрещено. [ТОЧКА].

Но ни один из ответов не предоставил экземпляр, который мог бы дать сбой в более ранних версиях, если бы java разрешил использование универсальных методов для необработанных экземпляров.

Может ли кто-нибудь помочь здесь, предоставив один конкретный случай, когда проблема возникла бы в более ранних версиях до 1.5, если бы они это разрешили?

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

Мне непонятны ваши рассуждения, но если у вас есть myMethod(A a) и myMethod(B b), то это два разных метода, которые были перегружены. Если вы попытаетесь перегрузить методы с помощью универсального параметра, они имеют одинаковую подпись из-за стирания типа.

matt 07.06.2019 16:54

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

VGR 07.06.2019 18:26
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
2
164
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Рассмотрим программу на Java, которая была написана для использования типов коллекций Java до версии Java 1.5.

До Java 1.5 я бы написал

 List l = new ArrayList();
 l.append("Hello");
 l.append("World");
 String hello = (String) l.get(0);

Теперь с моделью стирания типов я могу скомпилировать этот код в Java 1.5 или более поздней версии... даже без предупреждения компилятора. Но я также могу использовать те же самые классы коллекций современным способом; например

 List<String> l = new ArrayList<>();
 l.append("Hello");
 l.append("World");
 String hello = l.get(0);

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

Без модели стирания, чтобы замазать трещины, разработчикам Java пришлось бы создавать параллельный набор классов и интерфейсов для коллекций; то есть

  • Коллекции Pre-1.5 без дженериков и параметров типа
  • Коллекции после версии 1.5 с дженериками и параметрами типа

Поскольку эквивалентность типов Java основана на именах/идентификаторах типов, а не на эквивалентности на основе подписи (или утиной типизации), эти две иерархии коллекций будут несовместимы. Это означает, что API и реализации, использующие коллекции, должны будут использовать выберите между коллекциями до версии 1.5 и после версии 1.5. Смешивать их было бы неудобно... и неэффективно.

Конечный результат был бы большой проблемой для людей/организаций, которым нужно было использовать устаревшие библиотеки и приложения Java. По сути, переход на Java 1.5 означал бы много переписывания приложений, которые прекрасно работали. Это убило бы дженерики и Java как корпоративный язык.


Мы не можем привести вам конкретные примеры, которые доказуемо не будут работать на языке Java, где дженерики были основаны на шаблонах и/или не было стирания.

  • Этого гипотетического языка Java не существует.
  • При достаточных усилиях разработчиков любая программа Java до версии 1.5 могла быть перенесена на такой язык.

При добавлении дженериков в язык Java команда Java хотела удовлетворить следующие требования (среди прочего):

  • Код, скомпилированный для Java 4, и код, скомпилированный для Java 5, должны сосуществовать в одной и той же JVM и беспрепятственно взаимодействовать.
  • Код библиотеки, написанный для Java 4, должен иметь возможность переноса на Java 5 таким образом, чтобы не нарушалась двоичная совместимость.

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

Основная трудность возникла из-за модификации структуры коллекций Java API с помощью универсальных шаблонов таким образом, чтобы не нарушать существующий код. Например, рассмотрим библиотеку Java 4, которая объявляет:

public List getFactories();

и используется в существующем приложении Java 4, которое выполняет:

List list = getFactories();

Теперь предположим, что приложение обновлено до Java 5 раньше, чем библиотека. Параметр какого типа они указывают? Они могли сказать:

  • List factories = getFactories();

    ... но это лишает новый код возможности использовать дженерики

  • List<Object> factories = getFactories()

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

  • List<Factory> factories = getFactories();

    ... но это не было бы безопасным типом: поскольку среде выполнения не был сообщен параметр типа при создании объекта List, он не может проверить, что List содержит только фабрики (или обеспечить, чтобы это оставалось так, как унаследованный код продолжает общаться с List)

  • List<Factory> factories = new ArrayList<Factory>(getFactories);

    ... что бы сработало, но копирование всего списка может быть медленным

Команда Java выбрала вариант 3 и частично смягчила последствия, потребовав от компилятора предупреждения об отсутствии безопасности типов и заставив компилятор вставлять синтетические приведения для проверки типа каждого отдельного объекта в списке перед использованием, тем самым сохраняя целостность системы типа времени выполнения с небольшим ущербом для производительности (и значительной ценой здравомыслия разработчика, когда такое приведение не удалось).

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

Оглядываясь назад, я думаю, что команда .NET сделала здесь лучший выбор, потому что это позволило им развивать язык, а не увековечивать его ограничения. По крайней мере, обоснование их дизайнерских решений спустя десятилетие не нуждается в объяснении :-)

Другие вопросы по теме