Spec-Zone.ru › Java Language Specification 8

Глава 13. Бинарная совместимость

Оглавление

13.1. Формат бинарного файла
13.2. Что такое бинарная совместимость и что она не включает
13.3. Эволюция пакетов
13.4. Эволюция классов
13.4.1. abstract Классы
13.4.2. final Классы
13.4.3. public Классы
13.4.4. Родительские классы и интерфейсы
13.4.5. Параметры типа класса
13.4.6. Тело класса и объявления членов
13.4.7. Доступ к членам и конструкторам
13.4.8. Объявления полей
13.4.9. final Поля и static константы
13.4.10. static Поля
13.4.11. transient Поля
13.4.12. Объявления методов и конструкторов
13.4.13. Параметры типа методов и конструкторов
13.4.14. Формальные параметры методов и конструкторов
13.4.15. Тип результата метода
13.4.16. abstract Методы
13.4.17. final Методы
13.4.18. native Методы
13.4.19. static Методы
13.4.20. synchronized Методы
13.4.21. Исключение методов и конструкторов
13.4.22. Тело метода и конструктора
13.4.23. Перегрузка методов и конструкторов
13.4.24. Переопределение метода
13.4.25. Статические инициализаторы
13.4.26. Эволюция перечислений
13.5. Эволюция интерфейсов
13.5.1. public Интерфейсы
13.5.2. Родительские интерфейсы
13.5.3. Члены интерфейса
13.5.4. Параметры типа интерфейса
13.5.5. Объявления полей
13.5.6. Объявления методов интерфейса
13.5.7. Эволюция аннотаций

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

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

В рамках Бинарная совместимость при выпуске новых версий в SOM (Форман, Коннер, Данфорт и Репер, Труды OOPSLA '95), бинарные файлы языка программирования Java совместимы при всех соответствующих преобразованиях, которые авторы определяют (с некоторыми оговорками относительно добавления переменных экземпляров). Используя их схему, вот список некоторых важных бинарных совместимых изменений, которые поддерживает язык программирования Java:

  • Переопределение существующих методов, конструкторов и инициализаторов для повышения производительности.

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

  • Добавление новых полей, методов или конструкторов в существующий класс или интерфейс.

  • Удаление private полей, методов или конструкторов класса.

  • При обновлении всего пакета удаление полей, методов или конструкторов доступа к пакету для классов и интерфейсов в пакете.

  • Переупорядочение полей, методов или конструкторов в существующем объявлении типа.

  • Перемещение метода вверх по иерархии классов.

  • Переупорядочение списка прямых родительских интерфейсов класса или интерфейса.

  • Вставка новых типов классов или интерфейсов в иерархию типов.

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

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

В этой главе сначала определяются некоторые свойства, которые должен иметь любой бинарный формат для языка программирования Java (§13.1). Затем определяется бинарная совместимость, объясняя, что это такое и чего она не включает (§13.2). И, наконец, перечислены многие возможные изменения пакетов (§13.3), классов (§13.4) и интерфейсов (§13.5), определея какие из этих изменений гарантируют сохранение бинарной совместимости, а какие нет.

Иногда используются ссылки вида: (JVMS §x.y) для указания концепций из Спецификации виртуальной машины Java, издание Java SE 8.

13.1. Форма двоичного файла

Программы должны быть скомпилированы либо в формате файла class, определённом в Спецификации виртуальной машины Java, издание Java SE 8, либо в представление, которое может быть преобразовано в этот формат загрузчиком класса, написанным на языке программирования Java.

Полученный файл class должен иметь определённые свойства. Многие из этих свойств специально подобраны для поддержки преобразований исходного кода, сохраняющих двоичную совместимость. Требуемые свойства:

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

    • Двоичное имя верхнего уровня типа (§7.6) — это его каноническое имя (§6.7).

    • Двоичное имя вложенного типа (§8.5, §9.5) состоит из двоичного имени содержащего типа, за которым следует $, а затем — простым именем вложенного типа.

    • Двоичное имя локального класса (§14.3) состоит из двоичного имени содержащего типа, за которым следует $, затем — непустой последовательностью цифр, а затем — простым именем локального класса.

    • Двоичное имя анонимного класса (§15.9.5) состоит из двоичного имени содержащего типа, за которым следует $, а затем — непустой последовательностью цифр.

    • Двоичное имя параметра типа, объявленного обобщённым классом или интерфейсом (§8.1.2, §9.1.2) — это двоичное имя содержащего типа, за которым следует $, и затем — простым именем параметра типа.

    • Двоичное имя параметра типа, объявленного обобщённым методом (§8.4.4) — это двоичное имя класса, объявляющего метод, за которым следует $, затем — описанием метода (JVMS §4.3.3), за которым следует $, а затем — простым именем параметра типа.

    • Двоичное имя параметра типа, объявленного обобщённым конструктором (§8.8.4) — это двоичное имя класса, объявляющего конструктор, за которым следует $, затем — описанием конструктора (JVMS §4.3.3), за которым следует $, а затем — простым именем параметра типа.

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

  3. Ссылка на поле, являющееся константой (§4.12.4), должна быть разрешена во время компиляции до значения V, обозначаемого инициализатором константы.

    Если такое поле является static, то ссылка на поле не должна присутствовать в коде двоичного файла, включая класс или интерфейс, в котором объявлено поле. Такое поле должно всегда казаться инициализированным (§12.4.2); значение по умолчанию для поля (если оно отличается от V) никогда не должно наблюдаться.

    Если такое поле не является static, то ссылка на поле не должна присутствовать в двоичном файле, кроме класса, содержащего поле. (Это будет класс, а не интерфейс, так как интерфейс имеет только static поля.) Класс должен содержать код для установки значения поля в V во время создания экземпляра (§12.5).

  4. Дано законное выражение, обозначающее доступ к полю в классе C, ссылающееся на поле, названное f, которое не является константой и объявлено в (возможно, другом) классе или интерфейсе D, мы определяем тип квалификации ссылки на поле следующим образом:

    • Если выражение ссылается на простое имя, то если f является членом текущего класса или интерфейса C, то пусть T будет C. В противном случае, пусть T будет самым внутренним лексически окружающим типом объявления, членом которого является f. В любом случае, T является типом квалификации ссылки.

    • Если ссылка имеет вид ИмяТипа.f, где ИмяТипа обозначает класс или интерфейс, то класс или интерфейс, обозначаемый ИмяТипа, является типом квалификации ссылки.

    • Если выражение имеет вид ИмяВыражения.f или Основное.f, то:

      • Если тип выражения во время компиляции ИмяВыражения или Основное является типом пересечения V1 & ... & Vn (§4.9), то тип квалификации ссылки — V1.

      • В противном случае, тип выражения во время компиляции ИмяВыражения или Основное является типом квалификации ссылки.

    • Если выражение имеет вид super.f, то суперкласс C — это тип квалификации ссылки.

    • Если выражение имеет вид ИмяТипа.super.f, то суперкласс класса, обозначаемого ИмяТипа, является типом квалификации ссылки.

    Ссылка на f должна быть скомпилирована в символическую ссылку на стёртый вид (§4.6) типа квалификации ссылки, плюс простое имя поля, f. Ссылка также должна включать символическую ссылку на стёртый вид объявленного типа поля, чтобы верификатор мог проверить, что тип соответствует ожиданиям.

  1. В выражении вызова метода или выражении ссылки на метод в классе или интерфейсе C, ссылающемся на метод с именем m, объявленный (или неявно объявленный (§9.2)) в (возможно, отличном) классе или интерфейсе D, квалифицирующий тип вызова метода определяется следующим образом:

    • Если D является Object, то квалифицирующий тип выражения является Object.

    • В противном случае:

      • Если метод ссылается с помощью простого имени, то если m является членом текущего класса или интерфейса C, пусть T будет C; в противном случае, пусть T будет самым внутренним лексически окружающим типом объявления, членом которого является m. В любом случае, T является квалифицирующим типом вызова метода.

      • Если выражение имеет вид ИмяТипа.m или ТипСсылка::m, то тип, обозначаемый ИмяТипа или ТипСсылка, является квалифицирующим типом вызова метода.

      • Если выражение имеет вид ИмяВыражения.m или Первичное.m или ИмяВыражения::m или Первичное::m, то:

        • Если тип во время компиляции ИмяВыражения или Первичное является типом пересечения V1 & ... & Vn (§4.9), то квалифицирующий тип вызова метода — это V1.

        • В противном случае, тип во время компиляции ИмяВыражения или Первичное является квалифицирующим типом вызова метода.

      • Если выражение имеет вид super.m или super::m, то суперкласс C является квалифицирующим типом вызова метода.

      • Если выражение имеет вид ИмяТипа.super.m или ИмяТипа.super::m, то если ИмяТипа обозначает класс X, то суперкласс X является квалифицирующим типом вызова метода; если ИмяТипа обозначает интерфейс X, то X является квалифицирующим типом вызова метода.

    Ссылка на метод должна быть разрешена во время компиляции в символическую ссылку на стирание (§4.6) квалифицирующего типа вызова, плюс стирание подписи (§8.4.2) метода. Подпись метода должна включать все следующее, как определено в §15.12.3:

    • Простое имя метода

    • Количество параметров метода

    • Символическая ссылка на тип каждого параметра

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

  2. Для выражения создания экземпляра класса (§15.9) или явного вызова конструктора (§8.8.7.1) или выражения ссылки на метод вида ТипКласса :: new (§15.13) в классе или интерфейсе C, ссылающемся на конструктор m, объявленный в (возможно, отличном) классе или интерфейсе D, квалифицирующий тип вызова конструктора определяется следующим образом:

    • Если выражение имеет вид new D(...) или ИмяВыражения.new D(...) или Первичное.new D(...) или D :: new, то квалифицирующий тип вызова — это D.

    • Если выражение имеет вид new D(...){...} или ИмяВыражения.new D(...){...} или Первичное.new D(...){...}, то квалифицирующий тип выражения — это тип выражения во время компиляции.

    • Если выражение имеет вид super(...) или ИмяВыражения.super(...) или Первичное.super(...), то квалифицирующий тип выражения — это непосредственный суперкласс C.

    • Если выражение имеет вид this(...), то квалифицирующий тип выражения — это C.

    Ссылка на конструктор должна быть разрешена во время компиляции в символическую ссылку на стирание (§4.6) квалифицирующего типа вызова, плюс подпись конструктора (§8.8.2). Подпись конструктора должна содержать следующее:

    • Количество параметров конструктора

    • Символическую ссылку на тип каждого формального параметра

Двоичное представление класса или интерфейса также должно содержать следующее:

  1. Если это класс и он не является Object, то символическая ссылка на стирание непосредственного суперкласса этого класса.

  2. Символическая ссылка на стирание каждого непосредственного суперинтерфейса, если таковые имеются.

  3. Спецификация каждого поля, объявленного в классе или интерфейсе, представленная простым именем поля и символической ссылкой на стирание типа поля.

  4. Если это класс, то стёртая сигнатура каждого конструктора, как описано выше.

  5. Для каждого метода, объявленного в классе или интерфейсе (исключая, для интерфейса, неявно объявленные методы (§9.2)), его стёртая сигнатура и тип возвращаемого значения, как описано выше.

  6. Код, необходимый для реализации класса или интерфейса:

    • Для интерфейса, код инициализаторов полей и реализация каждого метода по умолчанию.

    • Для класса, код инициализаторов полей, инициализаторов экземпляров и статических инициализаторов, а также реализация каждого метода или конструктора.

  7. Каждый тип должен содержать достаточную информацию для восстановления его канонического имени (§6.7).

  8. Каждый тип вложенного класса должен содержать достаточную информацию для восстановления его модификатора доступа на уровне исходного кода.

  9. Каждый вложенный класс и вложенный интерфейс должен содержать символическую ссылку на ближайший содержащий класс (§8.1.3).

  10. Каждый класс должен содержать символические ссылки на все его типы членов (§8.5), а также на все локальные и анонимные классы, которые появляются в его методах, конструкторах, статических инициализаторах, инициализаторах экземпляров и инициализаторах полей.

    Каждый интерфейс должен содержать символические ссылки на все его типы членов (§9.5), а также на все локальные и анонимные классы, которые появляются в его методах по умолчанию и инициализаторах полей.

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

  12. Конструкции, сгенерированные компилятором Java, должны быть помечены как обязательные, если они соответствуют формальному параметру, неявно объявленному в исходном коде (§8.8.1, §8.8.9, §8.9.3, §15.9.5.1).

Следующие формальные параметры объявляются неявно в исходном коде:

  • Первый формальный параметр конструктора не-private вложенного внутреннего члена класса (§8.8.1, §8.8.9).

  • Первый формальный параметр анонимного конструктора анонимного класса, у которого суперкласс является внутренним или локальным (не в статическом контексте) (§15.9.5.1).

  • Формальный параметр name метода valueOf, который неявно объявлен в типе перечисления (§8.9.3).

Для справки, следующие конструкции объявляются неявно в исходном коде, но не помечаются как обязательные, потому что только формальные параметры могут быть помечены как таковые в файле class (JVMS §4.7.22):

  • Конструкторы по умолчанию для классов и типов перечислений (§8.8.9, §8.9.2)

  • Анонимные конструкторы (§15.9.5.1)

  • Методы values и valueOf типов перечислений (§8.9.3)

  • Определенные public поля типов перечислений (§8.9.3)

  • Определенные public методы интерфейсов (§9.2)

  • Аннотации контейнеров (§9.7.5)

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

13.2. Что такое и что не является бинарной совместимостью

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

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

Язык программирования Java разработан так, чтобы предотвратить добавление к контрактам и случайные коллизии имён, которые могут нарушить бинарную совместимость. В частности, добавление дополнительных методов перегрузки с определённым именем не нарушает совместимости с существующими бинарными файлами. Подпись метода, которую будет использовать существующий бинарный файл для поиска метода, выбирается алгоритмом разрешения перегрузки во время компиляции (§15.12.2).

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

Бинарная совместимость не то же самое, что и совместимость исходного кода. В частности, пример в §13.4.6 показывает, что набор совместимых бинарных файлов может быть получен из исходных кодов, которые не будут компилироваться вместе. Этот пример типичен: добавляется новое объявление, изменяющее значение имени в неизменённой части исходного кода, в то время как существующий бинарный файл для этой неизменённой части исходного кода сохраняет полностью квалифицированное, предыдущее значение имени. Для получения согласованного набора исходного кода необходимо предоставить квалифицированное имя или выражение доступа к полю, соответствующее предыдущему значению.

13.3. Эволюция пакетов

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

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

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

END_OF_DOCUMENT_MARKER

13.4. Эволюция классов

В этом разделе описываются последствия изменений в объявлении класса и его членов и конструкторов на ранее существующие бинарные файлы.

13.4.1. abstract классы

Если класс, который не был объявлен abstract, изменяется на объявление abstract, то ранее существующие бинарные файлы, пытающиеся создать новые экземпляры этого класса, выбросят либо исключение InstantiationError во время линковки, либо (если используется рефлексивный метод) исключение InstantiationException во время выполнения; такое изменение, следовательно, не рекомендуется для широко распространённых классов.

Изменение класса, объявленного abstract, на то, чтобы он больше не был объявлен abstract, не нарушает совместимость с ранее существующими бинарными файлами.

13.4.2. final классы

Если класс, который не был объявлен final, изменяется на объявление final, то будет выброшено исключение VerifyError, если загружается бинарный файл подкласса этого класса, потому что final классы не могут иметь подклассы; такое изменение не рекомендуется для широко распространённых классов.

Изменение класса, объявленного final, на то, чтобы он больше не был объявлен final, не нарушает совместимость с ранее существующими бинарными файлами.

13.4.3. public классы

Изменение класса, который не объявлен public, на объявление public, не нарушает совместимость с ранее существующими бинарными файлами.

Если класс, объявленный public, изменяется на то, чтобы он больше не был объявлен public, то будет выброшено исключение IllegalAccessError, если линкован ранее существующий бинарный файл, которому требуется, но больше нет доступа к типу класса; такое изменение не рекомендуется для широко распространённых классов.

13.4.4. Суперклассы и суперинтерфейсы

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

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

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

Пример 13.4.4-1. Изменение Суперкласса

Предположим, что следующая программа тестирования:

class Hyper { char h = 'h'; } 
class Super extends Hyper { char s = 's'; }
class Test extends Super {
    public static void printH(Hyper h) {
        System.out.println(h.h);
    }
    public static void main(String[] args) {
        printH(new Super());
    }
}

скомпилирована и выполнена, выведя на экран:

h

Предположим, что затем скомпилирована новая версия класса Super:

class Super { char s = 's'; }

Эта версия класса Super не является подклассом Hyper. Если мы затем запустим существующие бинарные файлы Hyper и Test с новой версией Super, то будет выброшено исключение VerifyError во время линковки. Верификатор возражает, потому что результат new Super() не может быть передан в качестве аргумента вместо формального параметра типа Hyper, потому что Super не является подклассом Hyper.

Поучительно рассмотреть, что могло бы произойти без шага верификации: программа могла бы выполнить и напечатать:

s

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

Вывод: реализация, в которой отсутствует верификатор или которая не использует его, не будет сохранять безопасность типов и, следовательно, не является действительной реализацией.


Требование, чтобы альтернативы в многовариантном операторе catch (§14.20) не являлись подклассами или суперклассами друг друга, является только ограничением для исходного кода. Если предположить, что следующий клиентский код является корректным:

try {
    throwAorB();
} catch(ExceptionA | ExceptionB e) {
    ...
}

где ExceptionA и ExceptionB не имеют отношения подкласса/суперкласса, когда клиент компилируется, то это бинарная совместимость по отношению к клиенту, если ExceptionA и ExceptionB имеют такое отношение, когда клиент выполняется.

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

13.4.5. Параметры типа класса

Добавление или удаление параметра типа класса само по себе не имеет никаких последствий для бинарной совместимости.

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

Переименование параметра типа класса не влияет на ранее существующие бинарные файлы.

Изменение первого ограничения параметра типа класса может изменить стирание (§4.6) любого члена, который использует этот параметр типа в своём собственном типе, и это может повлиять на бинарную совместимость. Изменение такого ограничения аналогично изменению первого ограничения параметра типа метода или конструктора (§13.4.13).

Изменение любого другого ограничения не влияет на бинарную совместимость.

13.4.6. Тело класса и объявления членов

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

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

Пример 13.4.6-1. Изменение тела класса

class Hyper {
    void hello() { System.out.println("hello from Hyper"); }
}
class Super extends Hyper {
    void hello() { System.out.println("hello from Super"); }
}
class Test {
    public static void main(String[] args) {
        new Super().hello();
    }
}

Эта программа выводит:

hello from Super

Предположим, что создана новая версия класса Super:

class Super extends Hyper {}

Затем, при повторной компиляции Super и выполнении этого нового бинарного файла с исходными бинарными файлами для Test и Hyper, выводится результат:

hello from Hyper

как ожидалось.


Ключевое слово super может использоваться для доступа к методу, объявленному в суперклассе, минуя любые методы, объявленные в текущем классе. Выражение super.Идентификатор разрешается на этапе компиляции в метод m в суперклассе S. Если метод m является методом-членом экземпляра, то метод, который вызывается во время выполнения, — это метод с той же сигнатурой, что и m, который является членом непосредственного суперкласса класса, содержащего выражение, включающее super.

Пример 13.4.6-2. Изменение Суперкласса

class Hyper {
    void hello() { System.out.println("hello from Hyper"); }
}
class Super extends Hyper { }
class Test extends Super {
    public static void main(String[] args) {
        new Test().hello();
    }
    void hello() {
        super.hello();
    }
}

Эта программа выводит:

hello from Hyper

Предположим, что создана новая версия класса Super:

class Super extends Hyper {
    void hello() { System.out.println("hello from Super"); }
}

Затем, если Super и Hyper скомпилированы заново, но не Test, при выполнении новых бинарных файлов с существующим бинарным файлом Test выводится результат:

hello from Super

как вы могли ожидать.


13.4.7. Доступ к членам и конструкторам

Изменение объявленного доступа к члену или конструктору, чтобы ограничить его, может нарушить совместимость с существующими двоичными файлами, вызвав ошибку компоновки при разрешении этих двоичных файлов. Доступ ограничен, если модификатор доступа изменён с пакета на private доступ; с protected доступа на пакет или private доступ; или с public доступа на protected, пакет или private доступ. Поэтому не рекомендуется изменять доступ к члену или конструктору для широко распространённых классов.

Возможно, неожиданно, что двоичный формат определён таким образом, что изменение доступа к члену или конструктору на более свободный не приводит к ошибке компоновки, когда подкласс (уже) определяет метод с менее свободным доступом.

Пример 13.4.7-1. Изменение доступа

Если пакет points определяет класс Point:

package points;
public class Point {
    public int x, y;
    protected void print() {
        System.out.println("(" + x + "," + y + ")");
    }
}

используемый программой:

class Test extends points.Point {
    public static void main(String[] args) {
        Test t = new Test();
        t.print();
    }
    protected void print() { 
        System.out.println("Test"); 
    }
}

то эти классы компилируются и Test выполняется, чтобы вывести результат:

Test

Если метод print в классе Point изменяется на public, и затем только класс Point перекомпилируется, а затем выполняется с ранее существующим двоичным файлом для Test, то ошибка компоновки не возникает. Это происходит даже несмотря на то, что на этапе компиляции некорректно переопределять метод public методом protected (как показано тем, что класс Test не может быть перекомпилирован с использованием нового класса Point, если print в Test не изменится на public.)


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

13.4.8. Объявления полей

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

Предположим ссылку на поле f с квалифицированным типом T. Предположим далее, что f фактически является полем экземпляра (соответственно static), объявленным в суперклассе T, S, и что тип f — X.

Если новое поле типа X с тем же именем, что и f, добавляется в подкласс S, который является суперклассом T или самим T, то может возникнуть ошибка компоновки. Такая ошибка компоновки произойдёт только если, кроме вышесказанного, выполняется одно из следующих условий:

  • Новое поле имеет меньший доступ, чем старое.

  • Новое поле — поле экземпляра (соответственно статическое) поле.

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

Пример 13.4.8-1. Добавление объявления поля

class Hyper { String h = "hyper"; }
class Super extends Hyper { String s = "super"; }
class Test {
    public static void main(String[] args) {
        System.out.println(new Super().h);
    }
}

Эта программа выводит:

hyper

Предположим, что создана новая версия класса Super:

class Super extends Hyper {
    String s = "super";
    int h = 0;
}

Затем, перекомпилируя Hyper и Super, и выполняя получившиеся новые двоичные файлы с предыдущим двоичным файлом Test, вывод будет следующим:

hyper

Поле h класса Hyper выводится исходным двоичным файлом Test. Хотя это может показаться удивительным с первого взгляда, это служит для уменьшения количества несовместимостей, возникающих во время выполнения. (В идеальном мире все исходные файлы, требующие перекомпиляции, перекомпилировались бы каждый раз, когда любой из них изменялся, устраняя такие сюрпризы. Но такая массовая перекомпиляция часто непрактична или невозможна, особенно в Интернете. И, как было отмечено ранее, такая перекомпиляция иногда потребовала бы дополнительных изменений в исходном коде.)

Ещё один пример, если программа:

class Hyper { String h = "Hyper"; }
class Super extends Hyper { }
class Test extends Super {
    public static void main(String[] args) {
        String s = new Test().h;
        System.out.println(s);
    }
}

скомпилируется и выполнится, то она выведет:

Hyper

Предположим, что затем скомпилирована новая версия класса Super:

class Super extends Hyper { char h = 'h'; }

Если полученный двоичный файл используется с существующими двоичными файлами для Hyper и Test, то вывод останется прежним:

Hyper

даже несмотря на то, что компиляция исходных кодов:

class Hyper { String h = "Hyper"; }
class Super extends Hyper { char h = 'h'; }
class Test extends Super {
    public static void main(String[] args) {
        String s = new Test().h;
        System.out.println(s);
    }
}

привела бы к ошибке компиляции, потому что h в исходном коде для main теперь будет рассматриваться как ссылка на поле char, объявленное в Super, и значение типа char нельзя присвоить полю типа String.


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

Для целей двоичной совместимости добавление или удаление поля f, тип которого включает переменные типов (§4.4) или параметризованные типы (§4.5) эквивалентно добавлению (соответственно, удалению) поля с тем же именем, тип которого — стирание (§4.6) типа поля f.

13.4.9. final Поля и static константы

Если поле, которое не было объявлено final, изменяется на объявление final, это может нарушить совместимость с существующими бинарными файлами, которые пытаются присвоить новые значения полю.

Пример 13.4.9-1. Изменение переменной на final

class Super { char s; }
class Test extends Super {
    public static void main(String[] args) {
        Super x = new Super();
        x.s = 'a';
        System.out.println(x.s);
    }
}

Эта программа выводит:

a

Предположим, что создана новая версия класса Super:

class Super { final char s = 'b'; }

Если Super перекомпилирован, но не Test, то запуск новой бинарной программы с существующей бинарной программой Test приводит к IllegalAccessError.


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

Если поле является константой (§4.12.4), и, кроме того, является static, то удаление ключевого слова final или изменение его значения не нарушит совместимость с существующими бинарными файлами, не вызвав их остановки, но они не увидят нового значения при использовании поля, если не перекомпилируются. Этот результат является побочным эффектом решения о поддержке условной компиляции (§14.21). (Можно предположить, что новое значение не будет видно, если использование происходит в константном выражении (§15.28), но будет видно в противном случае. Это не так; существующие бинарные файлы вообще не видят нового значения.)

Еще одна причина для требования внедрения значений static константных переменных заключается в switch операторах. Они являются единственным типом операторов, который полагается на константные выражения, а именно, что каждый case метка switch оператора должна быть константным выражением, значение которого отличается от каждого другого case метка. case метки часто являются ссылками на static константные переменные, поэтому может не быть очевидно, что все метки имеют разные значения. Если доказано, что при компиляции нет дублирующих меток, то внедрение значений в файл class гарантирует отсутствие дублирующих меток во время выполнения - очень желательное свойство.

Пример 13.4.9-2. Условная компиляция

Если пример:

class Flags { static final boolean debug = true; }
class Test {
    public static void main(String[] args) {
        if (Flags.debug)
            System.out.println("debug is true");
    }
}

скомпилирован и выполнен, он выводит:

debug is true

Предположим, что создана новая версия класса Flags:

class Flags { static final boolean debug = false; }

Если Flags перекомпилирован, но не Test, то запуск новой бинарной программы с существующей бинарной программой Test выводит:

debug is true

потому что значение debug было константным выражением и могло быть использовано при компиляции Test без ссылки на класс Flags.

Это поведение не изменится, если Flags будет изменён на интерфейс, как в изменённом примере:

interface Flags { boolean debug = true; }
class Test {
    public static void main(String[] args) {
        if (Flags.debug)
            System.out.println("debug is true");
    }
}

Условная компиляция обсуждается более подробно в конце §14.21.


Лучший способ избежать проблем с "изменчивыми константами" в широко распространённом коде - использовать static константные переменные только для значений, которые действительно, вероятно, никогда не изменятся. Помимо истинных математических констант, мы рекомендуем, чтобы исходный код использовал static константные переменные очень экономно.

Если требуется свойство read-only для final, лучшим выбором является объявление private static переменной и соответствующего метода доступа для получения её значения. Таким образом мы рекомендуем:


private static int N;
public static int getN() { return N; }

а не:


public static final int N = ...;

Нет проблем с:


public static int N = ...;

если N не обязательно должен быть read-only.

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

Мы отмечаем, но не рекомендуем, что если поле примитивного типа интерфейса может изменяться, его значение может быть выражено в соответствии с правилами:


interface Flags {
    boolean debug = new Boolean(true).booleanValue();
}

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

Ещё одна важная деталь заключается в том, что static константные переменные никогда не должны иметь значение по умолчанию для своего типа (§4.12.5). Это означает, что все такие поля, похоже, инициализируются в первую очередь во время инициализации класса (§8.3.2, §9.3.1, §12.4.2).

13.4.10. static Поля

Если поле, которое не было объявлено private, не было объявлено static, и изменяется на объявление static, или наоборот, то произойдёт ошибка связывания, конкретно ошибка IncompatibleClassChangeError, если поле используется существующей бинарной программой, которая ожидала поле другого типа. Такие изменения не рекомендуются в широко распространённом коде.

13.4.11. transient Поля

Добавление или удаление модификатора transient поля не нарушает совместимости с существующими бинарными файлами.

13.4.12. Объявления методов и конструкторов

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

Предположим ссылку на метод m с квалифицирующим типом T. Предположим далее, что m это, фактически, метод экземпляра (соответственно static) объявленный в суперклассе T, S.

Если новый метод типа X с той же сигнатурой и возвращаемым типом, что и m, добавляется в подкласс S, который является суперклассом T или самим T, может произойти ошибка связывания. Такая ошибка связывания произойдёт только если, помимо вышесказанного, верно одно из следующих:

  • Новый метод менее доступен, чем старый.

  • Новый метод является static (соответственно методом экземпляра).

Удаление метода или конструктора из класса может нарушить совместимость с любыми существующими бинарными файлами, которые ссылались на этот метод или конструктор; может быть выброшено исключение NoSuchMethodError при связывании такой ссылки из существующей бинарной программы. Такая ошибка произойдёт только если в суперклассе не объявлено ни одного метода с соответствующей сигнатурой и возвращаемым типом.

Если исходный код для не-вложенного класса не содержит объявленных конструкторов, то неявно объявляется конструктор по умолчанию без параметров (§8.8.9). Добавление одного или нескольких объявлений конструкторов в исходный код такого класса предотвратит неявное объявление этого конструктора по умолчанию, фактически удалив конструктор, за исключением случаев, когда один из новых конструкторов также не имеет параметров, таким образом заменив конструктор по умолчанию. Конструктор по умолчанию без параметров получает тот же модификатор доступа, что и класс его объявления, поэтому любая замена должна иметь не меньше или больше доступа, если необходимо сохранить совместимость с существующими бинарными файлами.

13.4.13. Параметры типа методов и конструкторов

Добавление или удаление параметра типа метода или конструктора само по себе не имеет последствий для двоичной совместимости.

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

Переименование параметра типа метода или конструктора не влияет на существующие двоичные файлы.

Изменение первого ограничения параметра типа метода или конструктора может изменить стирание (§4.6) любого члена, который использует этот параметр типа в собственном типе, и это может повлиять на двоичную совместимость. В частности:

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

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

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

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

Изменение любого другого ограничения не влияет на двоичную совместимость.

13.4.14. Формальные параметры методов и конструкторов

Изменение имени формального параметра метода или конструктора не влияет на существующие двоичные файлы.

Изменение имени метода или типа формального параметра метода или конструктора, или добавление параметра к объявлению метода или конструктора, или удаление параметра создаёт метод или конструктор с новой сигнатурой и имеет комбинированный эффект удаления метода или конструктора со старой сигнатурой и добавления метода или конструктора с новой сигнатурой (§13.4.12).

Изменение типа последнего формального параметра метода с T[] на параметр с переменным числом параметров (§8.4.1) типа T (т.е. на T...), и наоборот, не влияет на существующие двоичные файлы.

Для целей двоичной совместимости добавление или удаление метода или конструктора m, чья сигнатура включает переменные типов (§4.4) или параметризованные типы (§4.5), эквивалентно добавлению (соответственно, удалению) иначе эквивалентного метода, чья сигнатура является стиранием (§4.6) сигнатуры m.

13.4.15. Возвращаемый тип метода

Изменение возвращаемого типа метода, или замена возвращаемого типа на void, или замена void на возвращаемый тип, имеет комбинированный эффект удаления старого метода и добавления нового метода с новым возвращаемым типом или новым void возвращаемым типом (см. §13.4.12).

Для целей двоичной совместимости добавление или удаление метода или конструктора m, чьё возвращаемое значение включает переменные типов (§4.4) или параметризованные типы (§4.5), эквивалентно добавлению (соответственно, удалению) иначе эквивалентного метода, чьё возвращаемое значение является стиранием (§4.6) возвращаемого значения m.

13.4.16. abstract методы

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

Изменение метода, который не объявлен abstract, на метод, объявленный abstract, нарушит совместимость с существующими двоичными файлами, которые ранее вызывали этот метод, вызвав AbstractMethodError.

Пример 13.4.16-1. Изменение метода на abstract

class Super { void out() { System.out.println("Out"); } }
class Test extends Super {
    public static void main(String[] args) {
        Test t = new Test();
        System.out.println("Way ");
        t.out();
    }
}

Эта программа выводит:

Way
Out

Предположим, что новая версия класса Super создана:

abstract class Super {
    abstract void out();
}

Если Super перекомпилирована, но не Test, то запуск нового двоичного файла с существующим двоичным файлом Test приведёт к AbstractMethodError, потому что у класса Test нет реализации метода out и, следовательно, он (или должен быть) abstract.


13.4.17. final методы

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

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

Пример 13.4.17-1. Изменение метода на final

class Super { void out() { System.out.println("out"); } }
class Test extends Super {
    public static void main(String[] args) {
        Test t = new Test();
        t.out();
    }
    void out() { super.out(); }
}

Эта программа выводит:

out

Предположим, что новая версия класса Super создана:

class Super { final void out() { System.out.println("!"); } }

Если Super перекомпилирована, но не Test, то запуск нового двоичного файла с существующим двоичным файлом Test приведёт к VerifyError, так как класс Test неправильно пытается переопределить метод экземпляра out.


Изменение метода класса (static), который не объявлен final, на объявленный final, не нарушает совместимость с существующими двоичными файлами, поскольку метод не мог быть переопределён.

13.4.18. native методы

Добавление или удаление модификатора native метода не нарушает совместимость с существующими двоичными файлами.

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

13.4.19. static методы

Если метод, который не объявлен private, также объявлен static (т.е. метод класса) и изменён на не объявленный static (т.е. на метод экземпляра) или наоборот, совместимость с существующими двоичными файлами может быть нарушена, что приведёт к ошибке на этапе компоновки, а именно к IncompatibleClassChangeError, если эти методы используются существующими двоичными файлами. Такие изменения не рекомендуются в коде, который был широко распространён.

13.4.20. synchronized методы

Добавление или удаление модификатора synchronized метода не нарушает совместимость с существующими двоичными файлами.

13.4.21. Брошенные исключения методов и конструкторов

Изменения в throws фрагменте методов или конструкторов не нарушают совместимость с существующими двоичными файлами; эти фрагменты проверяются только на этапе компиляции.

13.4.22. Тело метода и конструктора

Изменения в теле метода или конструктора не нарушают совместимость с существующими бинарными файлами.

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

Поэтому следует отметить, что компилятор Java не может встраивать метод во время компиляции. В общем случае рекомендуется, чтобы реализации использовали создание и оптимизацию кода на этапе выполнения (late-bound).

13.4.23. Перегрузка методов и конструкторов

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

Хотя добавление нового перегруженного метода или конструктора может вызвать ошибку времени компиляции при следующем компилировании класса или интерфейса, так как нет метода или конструктора, который является наиболее специфичным (§15.12.2.5), такая ошибка не возникает при выполнении программы, поскольку разрешение перегрузки не выполняется во время выполнения.

Пример 13.4.23-1. Добавление перегруженного метода

class Super {
    static void out(float f) {
        System.out.println("float");
    }
}
class Test {
    public static void main(String[] args) {
        Super.out(2);
    }
}

Эта программа выводит:

float

Предположим, что создана новая версия класса Super:

class Super {
    static void out(float f) { System.out.println("float"); }
    static void out(int i)   { System.out.println("int");   }
}

Если Super перекомпилирован, но не Test, то запуск нового бинарного файла с существующим бинарным файлом Test все еще выводит:

float

Однако, если Test затем перекомпилирован, используя этот новый Super, вывод будет следующим:

int

как, возможно, и ожидалось в предыдущем случае.


13.4.24. Переопределение методов

Если к подклассу добавляется метод экземпляра, и он переопределяет метод в суперклассе, то метод подкласса будет найден вызовами методов в существующих бинарных файлах, и эти бинарные файлы не будут затронуты.

Если к классу добавляется статический метод, то этот метод не будет найден, если квалифицируемый тип ссылки не является типом подкласса.

13.4.25. Статические инициализаторы

Добавление, удаление или изменение статического инициализатора (§8.7) класса не влияет на существующие бинарные файлы.

13.4.26. Эволюция перечислений

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

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

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

13.5. Эволюция интерфейсов

В этом разделе описывается влияние изменений в объявлении интерфейса и его членов на существующие бинарные файлы.

13.5.1. public Интерфейсы

Изменение интерфейса, который не объявлен public, на объявление public, не нарушает совместимость с существующими бинарными файлами.

Если интерфейс, объявленный public, изменяется на необъявленный public, то будет выброшено исключение IllegalAccessError, если связан существующий бинарный файл, который нуждается, но больше не имеет доступа к типу интерфейса. Поэтому такие изменения не рекомендуется применять к широко распространённым интерфейсам.

13.5.2. Суперинтерфейсы

Изменения в иерархии интерфейсов вызывают ошибки аналогично изменениям в иерархии классов, как описано в §13.4.4. В частности, изменения, которые приводят к тому, что любой предыдущий суперинтерфейс класса больше не является суперинтерфейсом, могут нарушить совместимость с существующими бинарными файлами, что приведёт к возникновению VerifyError.

13.5.3. Члены интерфейса

Добавление abstract метода к интерфейсу не нарушает совместимости с существующими бинарными файлами.

Поле, добавленное в суперинтерфейс C, может скрывать поле, унаследованное от суперкласса C. Если исходная ссылка была на поле экземпляра, то произойдёт IncompatibleClassChangeError. Если исходная ссылка была присваиванием, то произойдёт IllegalAccessError.

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

Пример 13.5.3-1. Удаление члена интерфейса

interface I { void hello(); }
class Test implements I {
    public static void main(String[] args) {
        I anI = new Test();
        anI.hello();
    }
    public void hello() { System.out.println("hello"); }
}

Эта программа выведет:

hello

Предположим, что новая версия интерфейса I скомпилирована:

interface I {}

Если I перекомпилирована, но не Test, то запуск нового бинарного файла с существующим бинарным файлом для Test приведёт к NoSuchMethodError.


13.5.4. Параметры типа интерфейса

Воздействие изменений параметров типа интерфейса аналогично аналогичным изменениям параметров типа класса.

13.5.5. Объявления полей

Соображения по изменению объявлений полей в интерфейсах аналогичны соображениям для static final полей в классах, как описано в §13.4.8 и §13.4.9.

13.5.6. Объявления методов интерфейса

Соображения по изменению abstract объявлений методов в интерфейсах включают соображения для abstract методов в классах, как описано в §13.4.14, §13.4.15, §13.4.19, §13.4.21 и §13.4.23.

Добавление default метода или изменение метода из abstract в default не нарушает совместимости с существующими бинарными файлами, но может привести к возникновению IncompatibleClassChangeError, если существующий бинарный файл пытается вызвать метод. Эта ошибка возникает, если квалифицирующий тип T является подтипом двух интерфейсов I и J, где оба I и J объявляют default метод с одинаковой сигнатурой и результатом, и ни I, ни J не являются подинтерфейсами друг друга.

Другими словами, добавление метода по умолчанию является бинарно-совместимым изменением, потому что оно не вводит ошибок на этапе компоновки, даже если оно вводит ошибки на этапе компиляции или вызова. На практике риск случайных столкновений, возникающих при введении метода по умолчанию, аналогичен риску, связанному с добавлением нового метода в не-final класс. В случае столкновения добавление метода в класс, скорее всего, не вызовет LinkageError, но случайное переопределение метода в потомке может привести к непредсказуемому поведению метода. Оба изменения могут вызвать ошибки на этапе компиляции.

Пример 13.5.6-1. Добавление метода по умолчанию

interface Painter {
    default void draw() {
        System.out.println("Here's a picture...");
    }
}

interface Cowboy {}

public class CowboyArtist implements Cowboy, Painter {
    public static void main(String... args) {
        new CowboyArtist().draw();
   }
}

Эта программа выведет:

Here's a picture...

Предположим, что метод по умолчанию добавлен к Cowboy:

interface Cowboy {
    default void draw() {
        System.out.println("Bang!");
    }
}

Если Cowboy перекомпилирована, но не CowboyArtist, то запуск нового бинарного файла с существующим бинарным файлом для CowboyArtist будет связываться без ошибок, но приведёт к IncompatibleClassChangeError, когда main попытается вызвать draw().


13.5.7. Эволюция типов аннотаций

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

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

© Oracle and/or its affiliates. All rights reserved.
Licensed under the Oracle Technology Network License Agreement.

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API