Как будет использоваться контравариантность в Java-дженериках?

В Java ковариация позволяет дизайнеру API указывать, что экземпляр может быть обобщен как определенный тип или любой из подтипов этого типа. Например:

List<? extends Shape> shapes = new ArrayList<Circle>(); 
// where type Circle extends Shape

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

List<? super Shape> shapes = new ArrayList<Geometry>();
// where Shape extends Geometry

Как применима общая контравариантность Java? Когда вы решите использовать его?

Ответ 1

Хорошо, ваш второй пример позволит вам написать:

Shape shape = getShapeFromSomewhere();
shapes.add(shape);

тогда как вы не могли бы сделать это с первой формой. Это не полезно так часто, как ковариация, я дам вам.

Одна из областей, где это может быть полезно, - это сравнение. Например, рассмотрим:

class AreaComparer implements Comparator<Shape>
...

Вы можете использовать это для сравнения любых двух форм... так что было бы неплохо, если бы мы могли использовать его для сортировки List<Circle>, например. К счастью, мы можем сделать это с помощью контравариантности, поэтому существует перегрузка для Collections.sort of:

public static <T> void sort(List<T> list, Comparator<? super T> c)

Ответ 2

Вот соответствующий отрывок из Java Generics and Collections:

2.4. Принцип Get и Put

Может быть хорошей практикой вставлять подстановочные знаки, когда это возможно, но как вы решаете какой шаблон использовать? Где вы должны использовать extends, где следует использовать super, и где неуместно использовать подстановочный знак вообще?

К счастью, простой принцип определяет, что подходит.

Принцип Get and Put: используйте extends подстановочный знак, когда вы получаете значения из структуры, используйте superподстановочный знак, когда вы только ставите значения в структуру и не использовать подстановочный знак когда вы оба получаете и ставите.

Мы уже видели, что этот принцип работает в сигнатуре метода копирования:

public static <T> void copy(List<? super T> dest, List<? extends T> src)

Метод получает значения из исходного src, поэтому он объявляется с помощью шаблона extends, и он помещает значения в место назначения dst, поэтому он объявляется с помощью шаблона super. Всякий раз, когда вы используете итератор, вы получаете значения из структуры, поэтому используйте extends подстановочные. Вот метод, который берет набор чисел, преобразует каждый в двойной, и суммирует их:

public static double sum(Collection<? extends Number> nums) {
    double s = 0.0;
    for (Number num : nums) s += num.doubleValue();
    return s;
}

Ответ 3

Например, при реализации метода Collections.addAll() вам нужна коллекция, которая может содержать некоторый тип T или супертип T. Затем метод выглядит следующим образом:

public static <T> void addAll(Collection<? super T> collection, T... objects) {
    // Do something
}