Нижний ограниченный подстановочный знак (сопоставимый) - PullRequest
0 голосов
/ 14 февраля 2019

В приведениях коллекций к Comparable часто используется интерфейс, например.в PriorityQueue:

private static <T> void siftUpComparable(int k, T x, Object[] es) {
    Comparable<? super T> key = (Comparable<? super T>) x;
    ...
        if (key.compareTo((T) e) >= 0)
            break;
    ...
}

Скажем, мы создаем очередь целых чисел и добавляем что-то:

PriorityQueue<Integer> queue = new PriorityQueue<>();
queue.add(1);

Если я правильно понял концепцию подстановочных знаков, единственный эффект от использования <? super T> вместо <T> это то, что компилятор расширяет возможный тип аргумента compareTo

public interface Comparable<T> {
    public int compareTo(T o);
}

на любой суперкласс Integer.Но мне интересно, почему и как это можно использовать здесь.Любой пример, где

Comparable<T> key = (Comparable<T>) x;

недостаточно?Или это своего рода рекомендация использовать «супер» подстановочный знак с Comparable?

Ответы [ 3 ]

0 голосов
/ 14 февраля 2019

E может на самом деле не выставлять сопоставимый для своего класса.Представьте себе этот случай:

class Foo implements Comparable<Foo> { ... }

class Bar extends Foo { ... }

Если вы создаете приоритетную очередь PriorityQueue<Bar>, тогда вы используете метод сравнения, определенный в Foo.то есть, у вас есть Comparable<? super Bar>, где подстановочный знак реализован как Foo

0 голосов
/ 26 февраля 2019

Расслабление общей сигнатуры может повысить гибкость кода, особенно когда мы говорим о типах параметров.Общее руководство - это правило PECS , но для Comparable практические примеры необходимости такой гибкости редки, поскольку они подразумевают наличие базового типа, определяющего естественный порядок среди всех его подтипов.

Например, если у вас есть метод, подобный

public static <T extends Comparable<T>> void sort(Collection<T> c) {

}

, вы не можете выполнить следующее

List<LocalDate> list = new ArrayList<>();
sort(list); // does not compile

Причина в том, что LocalDate реализует ChronoLocalDate, и вы можете сравнить всереализации ChronoLocalDate друг с другом, другими словами, все они реализуют Comparable<ChronoLocalDate>.

Таким образом, сигнатура метода должна быть

public static <T extends Comparable<? super T>> void sort(Collection<T> c) {

}

, чтобы позволить фактическим типам реализовать Comparable параметризован с типом super, точно так же, как было объявлено Collections.sort.


Для конкретного случая siftUpComparable, т. Е. Внутренний метод Collection, у которого нет шансовдля определения фактической общей сигнатуры действительно не имеет значения, использует ли он Comparable<T> или Comparable<? super T>, поскольку все, что ему нужно, - это возможность передать экземпляр T методу compare, в то время как даже обеспечиваетфактический аргументв любом случае выполняется другим неконтролируемым приведением.

Фактически, поскольку приведение типа не проверено , оно могло бы быть также приведено к Comparable<Object>, что исключило бы необходимость выполнения(T) приведение при вызове compare.

Но, очевидно, автор не намеревался слишком далеко уходить от того, что будет делать настоящий универсальный код, и использовал тот же шаблон, даже если он не имееточевидная выгода здесь.

0 голосов
/ 14 февраля 2019

Весь смысл использования дженериков в том, что они безопасны от типов.Так как дженерики Java теряют информацию о типе во время выполнения, у нас нет метода проверки этого приведения.Ограниченные подстановочные знаки являются безопасным способом проверки ограничения типа во время компиляции, вместо того, чтобы вызывать преобразование Мэри во время выполнения.

Чтобы понять различную семантику super и extends в обобщениях, обратитесь к этой теме:
Что такое PECS (продюсер продлевает Consumer Super)?

...