Является ли он обратно совместимым для замены необработанного типа, например Collection с подстановочным знаком, например Collection <?> ?

Я являюсь автором некоторой библиотеки с открытым исходным кодом. Один из общедоступных интерфейсов имеет методы, которые используют необработанные типы, например Collection, например:

public StringBuilder append(..., Collection value);

Я получаю Collection is a raw type. References to generic type Collection<E> should be parameterized Collection is a raw type. References to generic type Collection<E> should be parameterized предупреждениями.

Я думаю об исправлении этих предупреждений. Реализации фактически не заботятся о типах элементов в коллекциях. Поэтому я думал о замене Collection<?>.

Однако эти методы являются частью публичного интерфейса моей библиотеки. Клиентский код может вызывать эти методы или обеспечивать собственные реализации этих открытых интерфейсов, реализуя таким образом эти методы. Я боюсь, что изменение Collection to Collection<?> Приведет к поломке клиентского кода. Вот мой вопрос.

Если я изменю CollectionCollection<?> В моих общедоступных интерфейсах, это может привести к:

  • ошибки компиляции в клиентском коде?
  • ошибки времени выполнения в уже скомпилированном существующем клиентском коде?

Ответы

Ответ 1

Эта замена небезопасна во время выполнения.

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

Разница между Collection и Collection<?> Заключается в том, что вы можете добавить что-либо к первому, тогда как вы не можете добавить ничего, кроме буквального null для последнего.

Итак, кто-то, кто в настоящее время переопределяет ваш метод, может сделать что-то вроде:

@Override
public StringBuilder append(Collection value) {
  value.add(Integer.valueOf(1));
  return new StringBuilder();
}

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

Теперь скажем, что этот метод называется так:

ArrayList c = new ArrayList();
thing.append(c);
c.get(0).toString();

(Опять же, я не знаю, как он используется по-настоящему. Потерпите меня)

Если вы внесли параметр append Collection<?> Вместо этого, возможно, удивительно (*), вам не нужно было бы обновлять подкласс, чтобы он был также общим: описанный выше метод append будет компилироваться.

Увидев новый общий тип параметра в базовом классе, вы могли бы подумать, что теперь вы можете сделать этот код вызова не сырым:

ArrayList<Double> c = new ArrayList<>();
thing.append(c);
c.get(0).toString();

Теперь, вот как получается последняя строка: в ней есть неявный листинг. На самом деле было бы оценено что-то вроде:

Double d = (Double) c.get(0);
d.toString();

Это несмотря на то, что вы можете вызывать toString() для Object: по-прежнему выполняется checkcast вставленная компилятором, в стирание типа элемента списка. Это закончилось бы во время выполнения, потому что последний элемент в списке - это целое число, а не Double.

И ключевым моментом является то, что для версии с типизированным типом не добавляется листинг. Это будет оцениваться следующим образом:

Object d = (Object) c.get(0);
d.toString();

Это не подведет во время выполнения, потому что что-то может быть передано объекту (на самом деле, вообще не было бы никакого броска, я просто вставляю его для симметрии).

Это не означает, что такой код вызова не мог существовать до создания параметра Collection<?>: Он, конечно, мог бы, и он будет работать с ошибкой во время выполнения. Но точка, которую я пытаюсь подчеркнуть, заключается в том, что создание этого параметра метода generic может дать ошибочное впечатление, что безопасно преобразовать существующий необработанный код вызова для использования генериков, и это приведет к сбою.

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


(*) Это возникает как следствие определения переопределенной эквивалентности в JLS Sec 8.4.2, где явно рассматривается стирание.

Ответ 2

У вас не будет никаких проблем во время выполнения, потому что общие типы стираются из двоичных файлов - см.: https://docs.oracle.com/javase/tutorial/java/generics/erasure.html

У вас также не будет проблем во время компиляции для Collection<?> И Collection эквивалентны. - см.: https://docs.oracle.com/javase/tutorial/extra/generics/legacy.html

Ответ 3

  1. Collection - это коллекция любого типа (то есть она может содержать любой тип элемента: Integer, String, Object...)
  2. Collection<?> - это коллекция определенного типа.

Клиентский код не будет иметь ошибок компиляции или ошибок времени выполнения. поскольку, когда вы передаете коллекцию в Collection<?> она рассматривается как Collection<Object>