Чтобы лучше понять концепцию, интересно видеть, что двоичная совместимость НЕ подразумевает совместимость API, а также наоборот.
API совместимо, но двоично не совместимы: статическое удаление
Version 1 из библиотеки: Код
public class Lib {
public static final int i = 1;
}
Клиента:
public class Main {
public static void main(String[] args) {
if ((new Lib()).i != 1) throw null;
}
}
компилировать код клиента с версией 1:
javac Main.java
Заменить версию 1 с версией 2: удалить static
:
public class Lib {
public final int i = 1;
}
Рекомпилированные только версия 2, не код клиента, и запустить java Main
:
javac Lib.java
java Main
Мы получаем:
Exception in thread "main" java.lang.IncompatibleClassChangeError: Expected static field Lib.i
at Main.main(Main.java:3)
Это происходит потому что хотя мы можем написать (new Lib()).i
в Java как для static
, так и для методов членов, он компилируется в две разные команды VM в зависимости от Lib
: getstatic
или getfield
. Этот разрыв упоминаются в JLS 7 13.4.10:
Если поле, которое не объявлено приватный не было объявлено статическими и изменено быть объявлено статическим, или наоборот, то ошибка связи, в частности, IncompatibleClassChangeError, приведет ли поле используется уже существующим двоичным кодом, которое ожидает поле другого типа.
Нам нужно перекомпилировать Main
с javac Main.java
для того, чтобы работать с новой версией.
Примечания:
- призывают статические члены из экземпляров класса, как
(new Lib()).i
плохой стиль, поднимает предупреждение, и никогда не должно быть сделано
- этот пример надуманный, так как не статические
final
примитивы бесполезны: всегда использование static final
для примитивов: private final static attribute vs private final attribute
- отражающий можно использовать to see the difference. Но отражение также может видеть частные поля, что, очевидно, приводит к разрыву, который не должен считаться перерывами, поэтому он не учитывается.
Binary совместимы, но НЕ API совместимы: нуль условием укрепления
Версия 1:
public class Lib {
/** o can be null */
public static void method(Object o) {
if (o != null) o.hashCode();
}
}
Версия 2:
public class Lib {
/** o cannot be null */
public static void method(Object o) {
o.hashCode();
}
}
Клиент:
public class Main {
public static void main(String[] args) {
Lib.method(null);
}
}
На этот раз, даже если перекомпилировать Main
после обновления Lib
, второй вызов будет брошен, но не первый.
Это потому, что мы сменили договор method
таким образом, который нельзя проверить во время компиляции Java: прежде чем он сможет принять null
, после этого больше нет.
Примечание:
- вика Затмение является отличным источником для этого: https://wiki.eclipse.org/Evolving_Java-based_APIs
- делает API, которые принимают
null
значения является сомнительной практикой
- это гораздо легче сделать изменения, нарушающее API совместимость, но не двоичная, чем наоборот, так как легко изменить внутреннюю логику методов
Http: // docs.oracle.com/javase/specs/jls/se7/html/jls-13.html –
Незаменимая ссылка при рассмотрении изменений, потенциально нарушающих двоичную совместимость: https: //wiki.eclipse.org/Evolving_Java-based_APIs_2 – jordanpg