Пожалуйста, покажите хороший пример ковариантности и контравариантности в Java.
Приведите примеры функций, демонстрирующих ковариантность и контравариантность в случаях как перегрузки, так и переопределения в Java?
Ответы (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", поэтому вы всегда можете передать один), но нет геттеров (возвращаемый тип может быть любого другого супертипа из Нить)
Ковариантность: 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)
Посмотрите на принцип замещения Лисков. По сути, если класс B расширяет класс A, вы должны иметь возможность использовать B всякий раз, когда требуется A.
contra variant
скажем. super.doSomething("String")
нельзя заменить на sub.doSomething(Object)
.
- person zinking; 26.01.2017