Spec-Zone.ru › Java Language Specification 21

Глава 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. Эволюция классов записей
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 (Форман, Коннер, Данфорт и Рейпер, Труды OOPSLA '95), двоичные файлы языка программирования Java обладают бинарной совместимостью при всех соответствующих преобразованиях, которые авторы идентифицируют (с некоторыми оговорками относительно добавления экземпляра переменных). Используя их схему, вот список некоторых важных изменений бинарной совместимости, которые поддерживает язык программирования Java:

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

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

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

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

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

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

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

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

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

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

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

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

END_OF_DOCUMENT_MARKER

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

Программы должны быть скомпилированы либо в формат файла class, указанный в спецификации Виртуальной машины Java, издание Java SE 21, либо в представление, которое может быть преобразовано в этот формат загрузчиком классов, написанным на языке программирования 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 класса путём добавления разрешённого непосредственного подкласса считается изменением, совместимым с двоичными файлами, поскольку предварительно созданные двоичные файлы, которые ранее связывались без ошибок (например, файл класса, содержащий исчерпывающий switch (§14.11.1)) будут продолжать связываться без ошибок. Файл класса, содержащий исчерпывающий switch, не будет прерывать связывание, если sealed класс, по которому он переключается, расширяется владельцем иерархии, чтобы иметь новый разрешенный непосредственный подкласс. JVM не обязана выполнять проверки исчерпываемости при связывании файла класса, содержащего исчерпывающий switch.

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

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

Например, требование, чтобы альтернативы в инструкции `multi-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, то может произойти ошибка линковки. Такая ошибка линковки произойдет только в том случае, если, помимо вышеперечисленного, выполняется одно из следующих условий:

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

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

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

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


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

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

13.4.11. Поля transient

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

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

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

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

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

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

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

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

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

13.4.20. Синхронизированные методы

Добавление или удаление модификатора 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. Эволюция классов перечислений

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

Как и в случае с sealed классами (§13.4.2.1), хотя добавление константы перечисления к классу перечисления считается бинарно совместимым изменением, это может привести к тому, что выполнение исчерпывающего switch (§14.11.1) завершится ошибкой, если switch столкнется с новой константой перечисления, которая не была известна на этапе компиляции (§14.11.3, §15.28.2).

Удаление константы перечисления из класса перечисления удалит поле 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, то возникает IncompatibleClassChangeError, если загружен двоичный файл подкласса или подинтерфейса этого интерфейса, который не является разрешенным прямым подклассом или подинтерфейсом этого интерфейса (§9.1.4); такое изменение не рекомендуется для широко распространенных классов.

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

Как и с sealed классами (§13.4.2.1), в то время как добавление разрешенного прямого подкласса или подинтерфейса sealed интерфейса считается двоично совместимым изменением, это может привести к выполнению исчерпывающего switch (§14.11.1) с ошибкой (может быть выброшен MatchException), если switch сталкивается с экземпляром нового разрешенного прямого подкласса или подинтерфейса, который не был известен на этапе компиляции (§14.11.3, §15.28.2).

Если класс или интерфейс удален из набора разрешенных прямых подклассов или подинтерфейсов 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