Ответ 1
Габор прав. Подстановочный знак позволяет статическому типу возвращаемого объекта отличаться от объявленного типа параметра введенной вами коллекции. Например, учитывая эти классы:
interface S extends Comparable<S> {}
class A implements S {
@Override
public int compareTo(final @NotNull S o) {
return 0;
}
}
class B implements S {
@Override
public int compareTo(final @NotNull S o) {
return 0;
}
}
И этот класс:
class Test {
@Nullable
static <T extends Comparable<? super T>> T extendsMax(
Collection<? extends T> coll) {
return null;
}
@Nullable
static <T extends Comparable<? super T>> T max(Collection<T> coll) {
return null;
}
}
Наблюдайте, какие вызовы компилируются и какие вызовы не выполняются:
public static void main(String[] args) {
final Collection<S> sColl = new ArrayList<>();
final Collection<A> aColl = new ArrayList<>();
final Collection<B> bColl = new ArrayList<>();
final S s1 = Test.<S> extendsMax(sColl); // compiles, T = S, <? extends T> = S
final S s2 = Test.<S> extendsMax(aColl); // compiles, T = S, <? extends T> = A
final S s3 = Test.<S> extendsMax(bColl); // compiles, T = S, <? extends T> = B
final A a1 = Test.<A> extendsMax(aColl); // compiles, T = A
final B b1 = Test.<B> extendsMax(bColl); // compiles, T = B
final S s4 = Test.<S> max(sColl); // compiles, T = S
final S s5 = Test.<S> max(aColl); // does not compile, T = S, T != A
final S s6 = Test.<S> max(bColl); // does not compile, T = S, T != B
final S s7 = Test.max(aColl); // compiles, but because T = A, and A
// can be assigned to S
}
Таким образом, подстановочный знак допускает некоторую гибкость. Хотя вы можете опустить подстановочный знак (и, честно говоря, я не могу думать о месте с верхней части головы, где требуется шаблон), есть причина, по которой он есть.
Том также неверен. Вы можете добавить null
в коллекции с подстановочным знаком (если коллекция поддерживает add()
в первую очередь):
List<? extends Number> list = new ArrayList<>();
list.add(null); // compiles, and should execute just fine
И поскольку add()
, remove()
и большинство других мутаторов в интерфейсе Collection
являются необязательными операциями, было бы безопасно мутировать коллекцию в любом случае с помощью этих методов, если параметр просто объявлен как Collection
. Кроме того, обычно можно использовать iterator().remove()
или что-то подобное для удаления элементов из коллекций независимо от того, были ли они объявлены с помощью шаблона, особенно для тех, которые уже включены в структуру коллекций Java.
Итак, в то время как подстановочный знак ограничивает то, что вы можете делать с коллекцией, он не должен использоваться как способ предотвращения изменений в коллекции.