Допустим, тип T может наследовать реализацию Comparable у своего базового класса. Зачем ограничивать T через Comparable<? super T>, а не через Comparable<T>?
Ограничение Comparable<? super T> разрешает использовать типы, сравнимые не только сами с собой, но и с любым своим супертипом. Поэтому алгоритм принимает, например, Employee, унаследовавший сравнение с Person. Ограничение Comparable<T> потребовало бы точной реализации Comparable<Employee> и отвергло бы такой корректный случай.
Обобщения появились в Java 5, чтобы библиотеки могли описывать типобезопасные алгоритмы без дублирования их для разных типов. При этом Java сохраняет модель совместимости со стиранием типов, поэтому важную роль играет точное описание отношений между типами, а не только их конкретные имена.
Паттерн Comparable<? super T> используется для согласования обобщённых алгоритмов с наследованием: базовый класс может определить естественный порядок, применимый ко всем его наследникам.
Предположим, Person реализует Comparable<Person>, а Employee просто наследуется от Person. Объекты Employee всё ещё можно корректно сравнивать как Person, но Employee не реализует Comparable<Employee>.
Если API потребует Comparable<T>, вызов для Employee будет отклонён компилятором, хотя сравнение семантически допустимо. Слишком строгое ограничение уменьшает повторное использование алгоритма и не отражает реальную совместимость наследования.
В записи T extends Comparable<? super T> параметр T обязан реализовывать Comparable<S>, где S — T или любой супертип T. Значит, метод compareTo принимает значение типа S, а объект T можно передать туда как объект его супертипа.
Минимальный пример:
Для T = Employee условие выполняется: Employee является Comparable<Person>, а Person — супертип Employee. Вариант T extends Comparable<T> не скомпилировался бы для Employee, потому что наследование Comparable<Person> не превращается в Comparable<Employee>.
Это не означает, что любой тип автоматически сравним: должен существовать согласованный контракт естественного порядка. Если порядок нельзя выразить через Comparable, более гибкой альтернативой является отдельный Comparator<? super T>.
В библиотечном методе требуется выбрать максимум из списка объектов. Вариант с ограничением Comparable<T> прост для классов, которые реализуют сравнение ровно со своим типом, но ломается на иерархиях, где порядок объявлен в базовом классе.
Вариант с Comparable<? super T> поддерживает и точное сравнение, и унаследованное сравнение. Это лучший выбор для общего алгоритма, поскольку он сохраняет типобезопасность и принимает больше корректных типов без небезопасных приведений.
Можно использовать внешний Comparator<? super T>: это позволяет выбирать разные критерии сортировки и не требует естественного порядка в самом классе. Но для операций, которым нужен именно естественный порядок, bound через Comparable<? super T> делает контракт метода явным и не требует отдельного аргумента.
1. Почему здесь используется именно super, а не extends?
T должен быть допустимым аргументом метода compareTo. Если объект реализует Comparable<S>, где S — супертип T, сравнение с S может принимать и значение T. Поэтому требуется нижняя граница wildcard: ? super T.
Comparable<? extends T> выражал бы другую связь: сравниваемый тип был бы неизвестным подтипом T, и передача произвольного T в такой compareTo была бы небезопасна.
2. Разрешает ли этот bound сравнивать объекты разных наследников одного базового класса?
Не обязательно. Если Cat и Dog оба наследуют Animal, реализующий Comparable<Animal>, каждый из них может удовлетворять bound отдельно. Но это не означает, что конкретный метод, принимающий List<Cat>, автоматически принимает List<Dog> или смешанный список.
Параметр T всё равно выбирается для одного вызова метода, а правила типизации коллекций и инвариантность сохраняются. Bound разрешает сравнение с указанным супертипом, но не отменяет остальные ограничения параметризации.
3. Когда лучше выбрать Comparator<? super T>, а не bound через Comparable?
Comparable подходит для единственного естественного порядка, являющегося свойством самого типа. Если порядок зависит от контекста — например, сотрудников можно сравнивать по имени, зарплате или дате найма, — лучше передавать Comparator<? super T>.
Comparator<? super T> также позволяет использовать компаратор базового типа для его наследников. Это сохраняет ту же полезную полиморфность, но отделяет алгоритм сравнения от модели данных.