Java: Разрешение имени / подписи метода выполняется статически (время компиляции)? - PullRequest
4 голосов
/ 10 февраля 2012

Сегодня я столкнулся с интересной проблемой, которая, по моему мнению, была невозможна в Java.Я скомпилировал свой java-код для jgroups версии 2.6, но использовал версию 2.12 во время выполнения (развертывание веб-приложения tomcat).Я получил следующую ошибку

org.jgroups.Message.<init>(Lorg/jgroups/Address;Lorg/jgroups/Address;Ljava/io/Serializable;)

Предполагая, что API с тех пор изменится, я подумал о переносе своего кода в jgroups-2.12, но, к моему удивлению, код скомпилирован нормально с jgroups-2.12, и когда язаменил новый jar (не меняя ни одной строки в моем коде, просто компилируя против jgroups-2.12 вместо jgroups-2.6), он работал отлично.

Позже я понял, что конструктор Message(Address, Address, Serializable) в 2.6 былизменено на Сообщение (Адрес, Адрес, Объект) в 2.12.Это означает, что во время выполнения JVM пыталась найти точно такой же метод и не смогла это сделать.

Означает ли это, что компилятор Java в процессе компиляции встраивает точное имя метода и точные аргументы, а также метод с более широкими аргументамине сработает?

Ответы [ 3 ]

4 голосов
/ 10 февраля 2012

Означает ли это, что компилятор Java встраивает точное имя метода и точные аргументы во время компиляции, и метод с более широкими аргументами не будет работать?

Точно. Вы также можете увидеть это из полученного сообщения об ошибке:

org.jgroups.Message.<init>(Lorg/jgroups/Address;Lorg/jgroups/Address;Ljava/io/Serializable;)

Здесь содержится полная подпись, и среда выполнения ищет идеальное совпадение.

Есть также несколько других случаев, когда изменение API нарушает двоичную совместимость в Java, но не совместимость с исходным кодом, например, когда вы меняете тип примитива на его коробочный вариант или наоборот. Как указал Джон, только изменения в Generics (но не все изменения) и использование синтаксиса VarArgs не влияют на разрешение метода времени выполнения, поскольку оба являются только функциями компилятора и не влияют на байт-код.

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

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

4 голосов
/ 10 февраля 2012

Да, это совершенно верно - точная подпись привязывается во время компиляции, и это то, что включается в байт-код.

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

По сути, если вы что-то измените в существующем члене общедоступного API, это будет серьезным изменением. Вы можете обойтись без некоторых языковых изменений, таких как изменение параметра String[] на параметр String... или введение обобщений (в некоторых случаях, если стирание совместимо с предыдущим кодом) , но это почти все.

Глава 13 Спецификации языка Java полностью посвящена бинарной совместимости - прочтите ее для получения дополнительной информации. Но, в частности, из раздел 13.4.14 :

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

1 голос
/ 10 февраля 2012

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

...