Как будет использоваться контравариантность в 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
}