Механизм covariant return types позволяет в Java при переопределении метода (overriding) возвращать не точно такой же тип, что и в суперклассе, а его подтип (covariant type).
Это повышает выразительность кода, позволяя получать более конкретные типы без явного приведения.
Пример:
class Animal { } class Dog extends Animal { } class Parent { Animal getAnimal() { return new Animal(); } } class Child extends Parent { @Override Dog getAnimal() { return new Dog(); } // Ковариантный возврат }
Условия для работы:
Можно ли в подклассе изменить возвращаемый тип метода-перегрузки (overloaded method), чтобы сделать его подтипом базового? Будет ли это считаться валидной перегрузкой?
Ответ: Нет, для перегрузки (overloading) важно только сигнатура по параметрам, а возвращаемый тип не играет роли. Менять только возвращаемый тип в перегрузке нельзя — такой метод будет конфликтовать по сигнатуре.
Пример:
class Example { Animal make() { return new Animal(); } // Dog make() { return new Dog(); } // Compile error: duplicate method! }
Ковариантность работает только для overriding (переопределения).
История
На проекте программист ошибся и добавил перегруженный метод с тем же именем и параметрами, но другим возвращаемым типом, ожидая, что это "ковариантное переопределение". В результате проект не компилировался, а баг выявили только при CI.
История
При использовании ковариантного return в иерархии классов, разработчик неверно применил параметризированные типы (generics), не реализуя правильно подтипы, в результате чего возникла ошибка ClassCastException во время выполнения при работе с коллекциями.
История
В переопределяемом методе возвращался более специализированный тип, но команда не задокументировала этот контракт. В коде, работавшем через базовый тип, возникали трудности с приведением типов и неожиданное поведение при "upcast" ссылок, что привело к ряду скрытых ошибок.