Продемонстрировать ковариацию и контравариантность в Java?

Пожалуйста, покажите хороший пример для ковариации и контравариантности в Java.

Ответ 1

ковариации:

class Super {
  Object getSomething(){}
}
class Sub extends Super {
  String getSomething() {}
}

Sub # getSomething является ковариантным, потому что он возвращает подкласс возвращаемого типа Super # getSomething (но полностью заполняет контракт Super.getSomething())

контрвариация

class Super{
  void doSomething(String parameter)
}
class Sub extends Super{
  void doSomething(Object parameter)
}

Sub # doSomething является контравариантным, потому что он принимает параметр суперкласса параметра Super # doSomething (но, опять же, заполняет контракт Super # doSomething)

Обратите внимание: этот пример не работает в Java. Компилятор Java перегружает и не переопределяет doSomething() - Method. Другие языки поддерживают этот стиль контравариантности.

Дженерики

Это также возможно для Generics:

List<String> aList...
List<? extends Object> covariantList = aList;
List<? super String> contravariantList = aList;

Теперь вы можете получить доступ ко всем методам covariantList, которые не принимают общий параметр (так как это должно быть что-то "extends Object" ), но getters будут работать нормально (поскольку возвращаемый объект всегда будет иметь тип "Object" )

Противоположно для contravariantList: вы можете получить доступ ко всем методам с помощью общих параметров (вы знаете, что это должен быть суперкласс "String", поэтому вы всегда можете передать один), но без геттеров (возвращаемый тип может быть любого другого супертипа String)

Ответ 2

Со-дисперсия: Итератор и Итератор. Почти всегда имеет смысл определить ко-вариант Iterable или Iterator. Iterator<? extends T> может использоваться так же, как Iterator<T> - единственное место, где появляется параметр типа - это тип возвращаемого значения из метода next, поэтому его можно безопасно поднять до T. Но если у вас S extends T, вы можете также присвоить Iterator<S> переменной типа Iterator<? extends T>. Например, если вы определяете метод find:

boolean find(Iterable<Object> where, Object what)

вы не сможете называть его List<Integer> и 5, поэтому его лучше определить как

boolean find(Iterable<?> where, Object what)

Contra-variance: Comparator. Практически всегда имеет смысл использовать Comparator<? super T>, потому что он может использоваться как Comparator<T>. Параметр type отображается только как тип параметра метода compare, поэтому T можно безопасно передать ему. Например, если у вас есть DateComparator implements Comparator<java.util.Date> { ... }, и вы хотите отсортировать List<java.sql.Date> с этим компаратором (java.sql.Date является подклассом java.util.Date), вы можете сделать это с помощью

<T> void sort(List<T> what, Comparator<? super T> how)

но не с

<T> void sort(List<T> what, Comparator<T> how)

Ответ 3

Посмотрите на принцип замены Лискова. Фактически, если класс B расширяет класс A, тогда вы должны иметь возможность использовать B, когда требуется A.