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


У меня есть следующий класс:

public abstract Foo {
  Foo() {}

  public abstract Foo doSomething();

  public static Foo create() {
    return new SomePrivateSubclassOfFoo();
  }
}

Я хочу изменить его на следующее определение:

public abstract Foo<T extends Foo<T>> {
  Foo() {}

  public abstract T doSomething();

  public static Foo<?> create() {
    return new SomePrivateSubclassOfFoo();
  }
}

Совместимо ли это изменение с двоичным кодом? Т. е. будет ли код, скомпилированный против старой версии класса, работать с новой версией без повторной компиляции?

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

В моем понимании, это должно быть нормально, потому что стирание T является Foo, и таким образом подпись doSomething в байтовом коде такая же, как и раньше. Если я посмотрю на внутренние подписи типа, напечатанные javap -s, я действительно увижу, что это подтверждается (хотя" не внутренние " подписи типа, напечатанные без -s, действительно отличаются). Я также проверил это, и это сработало для меня.

Однако Java API Compliance Checker говорит мне, что эти две версии не совместимы двоично.

Так что же правильно? Гарантирует ли JLS бинарную совместимость здесь, или мне просто повезло в моих тестах? (Почему это могло случиться?)

1 7

1 ответ:

Ну да, ваш код, похоже, не нарушает бинарную совместимость.
Я нашел их после некоторого ползания / чтения
http://docs.oracle.com/javase/specs/jls/se8/html/jls-13.html#jls-13.4.5 который говорит: -

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

И это http://wiki.eclipse.org/Evolving_Java-based_APIs_2#Turning_non-generic_types_and_methods_into_generic_ones далее уточняет: -

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

Таким образом, у вас нет проблем на данный момент, так как это первый раз, когда вы обобщаете этот класс.

но ... пожалуйста, имейте в виду , так как выше doc также говорит : -

Но также имейте в виду, что существуют серьезные ограничения на то, как тип или метод, который уже является универсальным, может быть совместим с его параметрами типа (см. таблицы выше). Поэтому, если вы планируете обобщить API, помните, что у вас есть только один шанс (release), чтобы получить его правильно. В частности, если вы измените тип в подписи API с необработанного типа " List "на" List> "или" List ", вы будете запертый в этом решении. Мораль заключается в том, что обобщение существующего API-это то, что должно рассматриваться с точки зрения API в целом, а не по частям на основе метода За методом или класса за классом.

Так что я думаю, это нормально, чтобы сделать это изменение в первый раз, но у вас есть только один шанс, так что используйте его в полной мере!