В Java List<S>
не является подтипом List<T>
, когда S
является подтипом T
. Это правило обеспечивает безопасность типов.
Допустим, мы разрешаем List<String>
быть подтипом List<Object>
. Рассмотрим следующий пример:
public void foo(List<Object> objects) {
objects.add(new Integer(42));
}
List<String> strings = new ArrayList<String>();
strings.add("my string");
foo(strings); // this is not allow in java
// now strings has a string and an integer!
// what would happen if we do the following...??
String myString = strings.get(1);
Итак, форсирование это обеспечивает безопасность типов, но оно также имеет недостаток, оно менее гибкое. Рассмотрим следующий пример:
class MyCollection<T> {
public void addAll(Collection<T> otherCollection) {
...
}
}
Здесь у вас есть коллекция T
, вы хотите добавить все элементы из другой коллекции. Вы не можете вызывать этот метод с Collection<S>
для S
подтипа T
. В идеале это нормально, потому что вы только добавляете элементы в свою коллекцию, но не изменяете коллекцию параметров.
Чтобы исправить это, Java предоставляет то, что они называют «подстановочными знаками». Подстановочные знаки являются способом обеспечения ковариации / контравариантности. Теперь рассмотрим следующее с использованием подстановочных знаков:
class MyCollection<T> {
// Now we allow all types S that are a subtype of T
public void addAll(Collection<? extends T> otherCollection) {
...
otherCollection.add(new S()); // ERROR! not allowed (Here S is a subtype of T)
}
}
Теперь с помощью подстановочных знаков мы допускаем ковариацию в типе T и блокируем операции, которые не являются безопасными для типа (например, добавление элемента в коллекцию). Таким образом мы получаем гибкость и безопасность типов.