Приведите примеры функций, демонстрирующих ковариантность и контравариантность в случаях как перегрузки, так и переопределения в Java?

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


person JavaUser    schedule 23.03.2010    source источник


Ответы (3)


Ковариация:

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(). Другие языки поддерживают этот стиль контравариантности.

Общие

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

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

Теперь вы можете получить доступ ко всем методам covariantList, которые не принимают универсальный параметр (поскольку он должен быть чем-то «расширяющим объект»), но геттеры будут работать нормально (поскольку возвращаемый объект всегда будет иметь тип "Объект")

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

person Hardcoded    schedule 23.03.2010
comment
Первый пример контравариантности не работает в Java. doSomething() в классе Sub является перегрузкой, а не переопределением. - person Craig P. Motlin; 15.04.2011
comment
Верно. Java не поддерживает контравариантные аргументы в подтипах. Ковариация только для того, что касается возвращаемых типов метода (как в первом примере). - person the_dark_destructor; 16.02.2012
comment
Отличный ответ. Ковариация кажется мне логичной. Но не могли бы вы указать мне абзац в JLS, который описывает контравариантность? Почему вызывается Sub.doSomething? - person Mikhail; 08.04.2013
comment
Как отметил Крейг, это не так. Я думаю, что здесь происходит столкновение между переопределением и перегрузкой, и SUN выбрала (как всегда) вариант с обратной совместимостью. Таким образом, в Java вы не можете использовать контравариантные параметры при переопределении метода. - person Hardcoded; 25.04.2013
comment
Было бы приятно узнать, почему я получаю отрицательные голоса за свой ответ. - person Hardcoded; 23.10.2015

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

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

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

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

Контрадисперсия: компаратор. Почти всегда имеет смысл использовать Comparator<? super T>, потому что его можно использовать так же, как Comparator<T>. Параметр типа отображается только как тип параметра метода 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)
person Yardena    schedule 23.03.2010

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

person extraneon    schedule 23.03.2010
comment
Это не отвечает на вопрос и вводит в заблуждение. Было бы вполне возможно разработать систему вариантов, которая нарушает семантическую правильность и, следовательно, нарушает LSP. - person Matt Whipple; 23.09.2016
comment
это не относится к contra variant скажем. super.doSomething("String") нельзя заменить на sub.doSomething(Object). - person zinking; 26.01.2017
comment
это не вопрос - person OlivierTerrien; 27.05.2018