Ответ 1
К сожалению, да, изменение метода void
для возврата чего-либо является нарушением изменений. Это изменение не повлияет на совместимость исходного кода (т.е. Тот же исходный код Java будет компилироваться так же, как и раньше, без какого-либо заметного эффекта), но он нарушает двоичную совместимость (т.е. байт-коды, которые ранее были скомпилированы против старого API, больше не будут выполняться).
Вот соответствующие выдержки из Спецификации Java Language Specification 3rd Edition:
13.2 Какая двоичная совместимость есть и не является
Совместимость двоичных файлов не совпадает с совместимостью с исходным кодом.
13.4 Эволюция классов
В этом разделе описываются эффекты изменений в объявлении класса и его элементов и конструкторов в ранее существовавших двоичных файлах.
13.4.15 Тип результата метода
Изменение типа результата метода, замена типа результата на
void
или заменаvoid
на тип результата имеет комбинированный эффект:
- удаление старого метода и
- добавление нового метода с новым типом результата или новым результатом
void
.13.4.12 Объявления по методу и конструктору
Удаление метода или конструктора из класса может нарушить совместимость с любым ранее существовавшим двоичным файлом, который ссылался на этот метод или конструктор; a
NoSuchMethodError
может быть брошен, когда такая ссылка из существующего двоичного файла связана. Такая ошибка будет возникать только в том случае, если в суперклассе объявлен не метод с подходящей сигнатурой и типом возврата.
То есть, в то время как возвращаемый тип метода игнорируется компилятором Java во время компиляции во время процесса разрешения метода, эта информация значительна во время выполнения на уровне байт-кода JVM.
В дескрипторах метода байт-кода
Подпись метода не включает тип возврата, но его дескриптор байт-кода делает.
8.4.2 Подпись метода
Два метода имеют одну и ту же подпись, если они имеют одинаковые имена и типы аргументов.
15.12 Выражения вызова метода
15.12.2 Время компиляции Шаг 2: Определение подписи метода
Дескриптор (тип подписи и возврата) наиболее конкретного метода используется во время выполнения для отправки метода.
15.12.2.12 Пример: Разрешение во время компиляции
Наиболее применимый метод выбирается во время компиляции; его дескриптор определяет, какой метод фактически выполняется во время выполнения.
Если к классу добавлен новый метод, исходный код, который был скомпилирован со старым определением класса, может не использовать новый метод, даже если перекомпиляция вызовет этот метод.
В идеале исходный код следует перекомпилировать, когда изменяется код, от которого он зависит. Однако в среде, где разные классы поддерживаются различными организациями, это не всегда возможно.
Небольшая проверка байт-кода поможет прояснить это. Когда javap -c
запускается в фрагменте перетасования имени, мы видим следующие инструкции:
invokestatic java/util/Arrays.asList:([Ljava/lang/Object;)Ljava/util/List;
\______________/ \____/ \___________________/\______________/
type name method parameters return type
invokestatic java/util/Collections.shuffle:(Ljava/util/List;)V
\___________________/ \_____/ \________________/|
type name method parameters return type
Связанные вопросы
О неразрывной модернизации
Теперь давайте рассмотрим, почему переоснащение нового interface
или vararg, как объяснено в Effective Java 2nd Edition, не нарушает двоичную совместимость.
13.4.4 Суперклассы и суперинтерфейсы
Изменение прямого суперкласса или набора прямых суперинтерфейсов типа класса не будет нарушать совместимость с ранее существовавшими двоичными файлами при условии, что общий набор суперклассов или суперинтерфейсов, соответственно, типа класса не теряет членства.
Дооснащение новой interface
не приводит к тому, что тип теряет какой-либо член, следовательно, это не нарушает двоичную совместимость. Аналогичным образом, из-за того, что varargs реализуется с использованием массивов, такой вид переоснащения также не нарушает двоичную совместимость.
8.4.1 Формальные параметры
Если последний формальный параметр является параметром переменной arty типа
T
, считается, что он определяет формальный параметр типаT[]
.
Связанные вопросы
Нет ли способа сделать это?
На самом деле, да, есть способ модифицировать возвращаемое значение ранее void
. Мы не можем иметь два метода с одинаковыми точными сигнатурами на уровне исходного кода Java, но мы можем иметь это на уровне JVM при условии, что у них разные дескрипторы (из-за наличия разных типов возвращаемых данных).
Таким образом, мы можем предоставить двоичный код, например. java.util.BitSet
, который имеет методы одновременно с типами возвратов void
и не void
. Нам нужно только опубликовать версию non void
в качестве нового API. Фактически, это единственное, что мы можем опубликовать в API, поскольку наличие двух методов с одной и той же сигнатурой является незаконным в Java.
Это решение является ужасным взломом, требующим специальной (и spec-defying) обработки для компиляции BitSet.java
в BitSet.class
, и, возможно, это не стоит того, чтобы это сделать.