Что такое принцип PECS в Java? - Вопросы
Всего: 7 вопросов
1. Что такое принцип PECS в Java?
Что такое принцип PECS в Java?
PECS (Producer Extends, Consumer Super) — правило выбора границы wildcard для параметра обобщённого типа. Решение принимается по роли параметра в методе:
- параметр — producer (источник), метод только читает из него элементы
T→<? extends T>; - параметр — consumer (приёмник), метод только записывает в него
T→<? super T>; - метод и читает, и пишет → точный тип
Tбез wildcard.
Короткая форма: достаёте — extends, кладёте — super. Название ввёл Джошуа Блох в «Effective Java» (3-е издание, Item 31). Пример: метод sum(List<Number>) не примет List<Integer>, потому что обобщённые типы инвариантны, а sum(List<? extends Number>) примет — список только читается.
2. Какие списки можно передать в параметр List<? extends Employee> и List<? super Employee> и что с ними можно делать внутри метода?
Какие списки можно передать в параметр List<? extends Employee> и List<? super Employee> и что с ними можно делать внутри метода?
Для иерархии Person ← Employee ← Manager:
List<? extends Employee>(upper bounded, producer) принимаетList<Employee>иList<Manager>.get()возвращаетEmployee, а добавить можно толькоnull:add(new Employee())даётincompatible types: Employee cannot be converted to CAP#1, ведь за параметром может стоятьList<Manager>.List<? super Employee>(lower bounded, consumer) принимаетList<Employee>,List<Person>,List<Object>. Добавлять можноEmployee,Managerиnull, аget()возвращает толькоObject— дальше нужна проверкаinstanceof.List<?>принимает любой список, читает элементы какObject, добавить можно толькоnull. Этим он отличается отList<Object>, в который добавлять можно что угодно, но передатьList<String>нельзя.
3. Почему в параметр List<? super Employee> можно передать List<Person>, но нельзя выполнить add(new Person())?
Почему в параметр List<? super Employee> можно передать List<Person>, но нельзя выполнить add(new Person())?
Это не исключение из PECS, а два следствия одной границы. Компилятор разрешает только операции, безопасные для любого списка, который может стоять за ? super Employee: List<Employee>, List<Person> или List<Object>.
add(new Employee())иadd(new Manager())безопасны: такой объект одновременноEmployee,PersonиObject.add(new Person())подошёл бы кList<Person>, но если переданList<Employee>, в него попал бы объект, не являющийсяEmployee. Компилятор не знает, какой список передан, и запрещает операцию.
Та же граница действует и при передаче аргумента, и при присваивании (List<? super Employee> list = persons; компилируется, = managers; — нет), и при добавлении элементов. Поэтому утверждение «PECS работает только при присваивании коллекций и не работает при добавлении» неверно.
4. Как применить PECS в собственном методе, который переносит элементы из одного списка в другой?
Как применить PECS в собственном методе, который переносит элементы из одного списка в другой?
PECS применяется к каждому параметру отдельно: источник получает extends, приёмник — super.
// candidates — producer, staff — consumer
static void hireAll(List<? extends Employee> candidates, List<? super Employee> staff) {
for (Employee e : candidates) {
staff.add(e);
}
}
List<Manager> managers = List.of(new Manager(), new Manager());
List<Person> persons = new ArrayList<>();
hireAll(managers, persons);
System.out.println(persons.size()); // 2
Если объявить оба параметра как List<Employee>, вызов hireAll(managers, persons) не скомпилируется ни по первому, ни по второму аргументу. С wildcard метод принимает максимум сочетаний типов и остаётся типобезопасным.
5. Как принцип PECS виден в сигнатурах стандартной библиотеки Java?
Как принцип PECS виден в сигнатурах стандартной библиотеки Java?
Методы JDK, которые читают из одного параметра и пишут в другой, объявлены по PECS:
Collections.copy(List<? super T> dest, List<? extends T> src)—srcproducer,destconsumer;Collections.addAll(Collection<? super T> c, T... elements)— коллекция consumer;Collection.addAll(Collection<? extends E> c)— аргумент producer;List.sort(Comparator<? super E> c)— компаратор consumer элементов;Collections.max(Collection<? extends T> coll, Comparator<? super T> comp)— коллекция producer, компаратор consumer.
Comparator<T> принимает объекты в compare(), то есть потребляет их, поэтому его объявляют как ? super T. Благодаря этому один Comparator<Object>, например Comparator.comparing(Object::toString), сортирует и List<Integer>: [10, 9, 100] превращается в [10, 100, 9] (сравнение строк).
6. Почему Collections.copy() не работает с пустым списком-приёмником, хотя типы по PECS подходят?
Почему Collections.copy() не работает с пустым списком-приёмником, хотя типы по PECS подходят?
PECS отвечает только за типы, а не за размер. Collections.copy(dest, src) заменяет элементы dest по индексам и не добавляет новые: если dest.size() < src.size(), бросается IndexOutOfBoundsException: Source does not fit in dest. Если dest длиннее, хвост остаётся: копирование [3, 1, 2] в List<Number> [0, 0, 0, 0.5] даёт [3, 1, 2, 0.5].
Для копирования в пустой список используйте dest.addAll(src) или new ArrayList<>(src).
7. Какие ошибки чаще всего допускают при использовании PECS и wildcard?
Какие ошибки чаще всего допускают при использовании PECS и wildcard?
- Ожидать, что
List<Manager>— подтипList<Employee>. Обобщённые типы инвариантны; параметр-источник объявляют какList<? extends Employee>. - Добавлять в
List<? extends T>— ошибкаcannot be converted to CAP#1. Если метод пишет в коллекцию, нужен? super Tили точныйT. - Считать
? extendsзащитой от изменений:remove(),clear()иadd(null)работают. Для неизменяемости —List.copyOf()илиCollections.unmodifiableList(). - Читать из
List<? super T>какT— элемент имеет типObject. Если нужно и читать какT, и писать — точный типList<T>. - Ставить wildcard в возвращаемый тип (
List<? extends Number> load()): вызывающий код не сможет ничего добавить в результат. Wildcard — только в параметрах. - Пытаться задать две границы:
<? extends A super B>не компилируется — у wildcard одна граница.