Spec-Zone.ru › Java Language Specification 17

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

Содержание

13.1. Формат бинарного файла
13.2. Что такое и что не является бинарной совместимостью
13.3. Эволюция пакетов и модулей
13.4. Эволюция классов
13.4.1. abstract классы
13.4.2. sealed, non-sealed и final классы
13.4.2.1. sealed классы
13.4.2.2. non-sealed классы
13.4.2.3. 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.4.27. Эволюция record-классов
13.5. Эволюция интерфейсов
13.5.1. public интерфейсы
13.5.2. sealed и non-sealed интерфейсы
13.5.3. Суперинтерфейсы
13.5.4. Члены интерфейса
13.5.5. Параметры типа интерфейса
13.5.6. Объявления полей
13.5.7. Объявления методов интерфейса
13.5.8. Аннотационные интерфейсы

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

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

В рамках Бинарной совместимости при переходе к новым версиям в SOM (Forman, Conner, Danforth и Raper, Труды 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 17 издание.

END_OF_DOCUMENT_MARKER

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

Программы должны быть скомпилированы либо в формат файлов class, указанный в спецификации Java Virtual Machine Specification, Java SE 17 Edition, либо в представление, которое может быть преобразовано в этот формат загрузчиком классов, написанным на языке программирования 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, то пусть Q будет C. Иначе, пусть Q будет самым вложенным лексически охватывающим классом или интерфейсом, членом которого является f. В любом случае, Q — квалифицирующий класс или интерфейс ссылки.

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

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

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

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

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

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

    Ссылка на f должна быть скомпилирована в символьную ссылку на квалифицирующий класс или интерфейс ссылки, плюс простое имя поля f.

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

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

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

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

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

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

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

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

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

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

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

    Ссылка на метод должна быть разрешена во время компиляции в символическую ссылку на квалифицирующий класс или интерфейс вызова метода, плюс стирание объявленной сигнатуры (§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.

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

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

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

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

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

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

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

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

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

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

    • Для интерфейса, код инициализаторов поля и реализация каждого метода с блочным телом (§9.4.3).

    • Для класса, код инициализаторов поля, инициализаторов экземпляра и статических инициализаторов, реализация каждого метода с блочным телом (§8.4.7) и реализация каждого конструктора.

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

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

  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).

  • Формальные параметры компактного конструктора класса записи (§8.10.4).

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

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

  • Канонические конструкторы классов записи (§8.10.4)

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

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

  • Определённые public поля перечислительных классов (§8.9.3)

  • Определённые private поля и public методы классов записи (§8.10.3)

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

  • Контейнерные аннотации (§9.7.5)

Файл class, соответствующий объявлению модуля, должен иметь свойства файла class для класса, бинарное имя которого module-info, и который не имеет суперклассов, суперинтерфейсов, полей и методов. Кроме того, двоичное представление модуля должно содержать все следующее:

  • Спецификация имени модуля, заданная как символическая ссылка на имя, указанное после module. Также спецификация должна включать, является ли модуль обычным или открытым (§7.7).

  • Спецификация каждой зависимости, обозначенной директивой requires, заданная как символическая ссылка на имя модуля, указанное в директиве (§7.7.1). Также спецификация должна включать, является ли зависимость transitive и является ли зависимость static.

  • Спецификация каждого пакета, обозначенного директивой exports или opens, заданная как символическая ссылка на имя пакета, указанного в директиве (§7.7.2). Также, если директива была квалифицирована, спецификация должна предоставить символические ссылки на имена модулей, указанные в to-разделе директивы.

  • Спецификация каждой службы, обозначенной директивой uses, заданная как символическая ссылка на имя класса или интерфейса, указанного в директиве (§7.7.3).

  • Спецификация поставщиков услуг, обозначенных директивой provides, заданная как символические ссылки на имена классов и интерфейсов, указанные в with-разделе директивы (§7.7.4). Также спецификация должна предоставить символическую ссылку на имя класса или интерфейса, указанного как служба в директиве.

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

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

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

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

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

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

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

13.3. Развитие пакетов и модулей

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

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

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

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

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

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

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

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

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

13.4.1. abstract классы

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

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

13.4.2. sealed, non-sealed и final классы

13.4.2.1. sealed классы

Если класс, который был свободно расширяемым (§8.1.1.2), изменяется на объявление sealed, то IncompatibleClassChangeError выбрасывается, если двоичный файл существующего подкласса этого класса загружается и не является разрешенным прямым подклассом этого класса (§8.1.6); такое изменение не рекомендуется для широко распространённых классов.

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

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

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

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

Если у запечатанного класса C был sealed прямой суперкласс или sealed прямой суперинтерфейс, то удаление модификатора sealed предотвратит перекомпиляцию C, так как каждый класс с sealed прямым суперклассом или sealed прямым суперинтерфейсом должен быть либо final, либо sealed, либо non-sealed.

13.4.2.2. non-sealed классы

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

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

non-sealed класс C должен иметь sealed прямой суперкласс или sealed прямой суперинтерфейс (§8.1.1.2). Удаление модификатора non-sealed предотвратит перекомпиляцию C, так как каждый класс с sealed прямым суперклассом или sealed прямым суперинтерфейсом должен быть либо final, либо sealed, либо non-sealed.

13.4.2.3. final классы

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

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

Если final класс C имел sealed прямой суперкласс или sealed прямой суперинтерфейс, то удаление модификатора final предотвратит перекомпиляцию C, так как каждый класс с sealed прямым суперклассом или sealed прямым суперинтерфейсом должен быть либо final, либо sealed, либо non-sealed (§8.1.1.2).

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.

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


Пример 13.4.4-2. Введение суперкласса

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

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

try {
    failByThrowingAorB();
} catch (A|B e) {
    ...
}

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


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 с квалифицирующим классом C. Далее предположим, что f — это фактически поле-инстанс (соответственно static), объявленное в суперклассе C, S, и что тип f — X.

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

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

  • Новое поле является полем static (соответственно, полем-инстансом).

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

Пример 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.


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

Для целей бинарной совместимости добавление или удаление поля 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.22). (Можно предположить, что новое значение не видно, если используется в константном выражении (§15.29), но видно в противном случае. Это не так; существующие двоичные файлы вообще не видят нового значения.)

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

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


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

вместо:


public static final int N = ...;

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


public static int N = ...;

если N не обязательно должен быть только для чтения.

13.4.10. static Поля

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

13.4.11. transient Поля

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Для целей двоичной совместимости добавление или удаление метода или конструктора 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 приведёт к IncompatibleClassChangeError, потому что класс 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-компилятор не может встроить метод в режиме компиляции. В общем, мы рекомендуем реализациям использовать генерацию и оптимизацию кода на поздних этапах (во время выполнения).

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. Развитие классов перечислений

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

Удаление константы перечисления из класса перечисления удалит поле public, соответствующее константе перечисления (§8.9.3). Последствия описаны в §13.4.8. Такое изменение не рекомендуется для широко распространённых классов перечислений.

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

13.4.27. Эволюция классов записей

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

Более точно, добавление, удаление, изменение или переупорядочивание компонентов записи может изменить соответствующие неявные объявления полей компонентов и методов-доступов, а также изменить сигнатуру и реализацию канонического конструктора и других поддерживающих методов, со следствиями, указанными в §13.4.8 и §13.4.12.

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

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

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

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

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

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

13.5.2. sealed и non-sealed интерфейсы

Если интерфейс, свободно расширяемый (§9.1.1.4), изменяется на интерфейс, объявленный sealed, то при загрузке двоичного файла подкласса или подинтерфейса этого интерфейса, который не является разрешённым прямым подклассом или подинтерфейсом этого интерфейса (§9.1.4), выбрасывается IncompatibleClassChangeError. Такие изменения не рекомендуются для широко распространённых классов.

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

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

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

non-sealed интерфейс I должен иметь sealed прямой суперинтерфейс. Удаление модификатора non-sealed помешает перекомпиляции I, так как каждый интерфейс с sealed прямым суперинтерфейсом должен быть sealed или non-sealed.

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

Если для запечатанного интерфейса I был sealed прямой суперинтерфейс, то удаление модификатора sealed помешает перекомпиляции I, так как каждый интерфейс с sealed прямым суперинтерфейсом должен быть sealed или non-sealed.

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

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

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

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

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

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

Пример 13.5.4-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.5. Параметры типа интерфейса

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

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

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

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

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

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

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

Пример 13.5.7-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.8. Интерфейсы аннотаций

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

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

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

Spec-Zone.ru

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