PECS для дженериков в неколлекциях

Джошуа Блох придумал PECS, в которой указано правило, когда использовать ? extends T и ? super T. Если вы думаете о PECS с точки зрения структуры коллекций, то все очень просто. Если вы добавляете значения в структуру данных, используйте ? super T. Если вы читаете из структуры данных, используйте ? extends T. например:

public class Collections {  
    public static <T> void copy(List<? super T> dest, List<? extends T> src) {  
        for (int i = 0; i < src.size(); i++)   
            dest.set(i, src.get(i));   
    }   
}

Если я проверю подпись

public static <T> void sort(List<T> list, Comparator<? super T> c) 

Я вижу, что Comparator использует ? super, поэтому он должен быть потребителем. Глядя на код, компаратор c используется только для создания материала, потому что ему задают логику сравнения.

С одной стороны, я понимаю, почему это супер, потому что как разработчик я хочу использовать компараторы класса T, а также компараторы суперкласса T, потому что объекты T также относятся к типу суперклассов T. Но когда я пытаюсь думать с точки зрения PECS, я ничего не понимаю.

Подходит ли PECS только для структуры коллекций? Если нет, может кто-нибудь объяснить мне, что потребляет компаратор в Collections.sort?

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
2
0
228
1

Ответы 1

Ради этого ответа возьмем Comparator в качестве основного, руководящего примера.

Если вы хорошенько об этом подумаете, то увидите, что Comparator на самом деле получает двух аргументов типа T и возвращает результат их сравнения (представленный int). Другими словами, это потребляет два экземпляра типа T и производит значение int. Итак, согласно правилу PECS, это потребительT, отсюда и использование ? super T.

В более общем смысле следует рассматривать режиссер и потребитель с точки зрения основного типа в отношении типов каждого из его универсальных параметров. Если некоторые объекты Comparator типа потребляет типа T, правило PECS гласит, что пользователи такого Comparator<T> могут использовать его для сравнения объектов, тип которых является подтипом T.

В качестве конкретного примера, если у вас уже есть логика для сравнения двух общих экземпляров Number (независимо от того, каков их конкретный тип на самом деле), вы можете использовать ее, т. е. для сравнения экземпляров Double, потому что двойные значения, в конце концов, числа.

Рассмотрим следующий компаратор:

Comparator<Number> c = Comparator.comparingInt(Number::intValue);

Здесь компаратор c сравнивает экземпляры Number (номер любой), принимая во внимание только их интегральную часть.

Если у вас есть следующий список экземпляров Double:

List<Double> doubles = Arrays.asList(2.2, 2.1, 7.3, 0.2, 8.4, 9.5, 3.8);

И следующий метод sort:

static <T> void sort(List<T> list, Comparator<T> c) {
    list.sort(c);
}

(Обратите внимание на отсутствие подстановочного знака ? super T в аргументе Comparator).

Затем, если вы хотите отсортировать список List<Double> doubles, сигнатура вышеуказанного метода sort потребует от вас передачи конкретного Comparator<Double>. Но что, если вы хотите использовать свой ранее определенный компаратор c для сортировки List<Double> doubles?

Поскольку тип этого компаратора — Comparator<Number>, а тип списка doublesList<Double>, следующий код вызовет ошибку компиляции:

sort(doubles, c);

К счастью, поскольку Comparator является потребитель типа сравниваемых элементов, вы можете изменить сигнатуру метода sort на:

static <T> void sort(List<T> list, Comparator<? super T> c) {
    list.sort(c);
}

И теперь этот код будет компилироваться:

sort(doubles, c);

Большое спасибо за супер чистое объяснение. Я хочу привести еще один пример с Iterator<T>. Если я разработаю метод, который вызывает T next(), тогда я должен создать свою подпись с помощью ? расширяется, потому что итератор теперь производит T. Его можно обобщить, как если бы общий класс в методе потреблял T (как в случае компаратора), он должен быть супер, если он возвращает ссылку на T (как в случае итератора), тогда он должен быть продлен.

Fatih Arslan 07.02.2019 20:26

@FatihArslan Да, это правильно. Например, если у вас есть метод, который получает Iterator<Number>, и вы пытаетесь передать Iterator<Integer>, он не скомпилируется. Но если вы измените метод так, чтобы теперь он получал Iterator<? extends Number>, вы сможете без проблем передать Iterator<Integer>. И подстановочные знаки тоже можно смешивать. Например, метод Stream.map получает Function<? super T, ? extends R>.

fps 07.02.2019 22:14

Другие вопросы по теме