Формально, ковариантность/контравариантность типов – это сохранение/обращение порядка наследования для производных типов. Проще говоря, когда у ковариантных сущностей типами-параметрами являются родитель и наследник, они сами становятся как бы родителем и наследником. Контравариантные наоборот, становятся наследником и родителем.
Легче всего осознать эти понятия на примерах:
- Ковариантность:
List<Integer> можно присвоить в переменную типа List<? extends Number> (как будто он наследник List<Number>). - Контравариантность: в качестве параметра метода
List<Number>#sort типа Comparator<? super Number> может быть передан Comparator<Object> (как будто он родитель Comparator<Number>).
Отношение типов «можно присвоить» – не совсем наследование, такие типы называются
совместимыми (отношение «is a»).
Существует еще одно связанное понятие –
инвариантность. Инвариантность – это
отсутствие свойств ковариантности и контрвариантности. Дженерики без вайлдкардов инвариантны:
List<Number> нельзя положить ни в переменную типа
List<Double>, ни в
List<Object>.
Массивы ковариантны: в переменную
Object[] можно присвоить значение типа
String[].
Переопределение методов начиная с Java 5 ковариантно относительно типа результата и типов исключений.