Что такое PECS (производитель расширяет Consumer Super)?
Я наткнулся на PECS (сокращение от Producer extends
и Consumer super
), читая о дженериках.
Кто-нибудь может мне объяснить, как использовать PECS для устранения путаницы между extends
и super
?
Переведено автоматически
Ответ 1
tl; dr: "PECS" - это с точки зрения коллекции. Если вы только извлекаете элементы из универсальной коллекции, это производитель, и вы должны использовать extends
; если вы только загружаете элементы, это потребитель, и вы должны использовать super
. Если вы делаете оба с одной и той же коллекцией, вам не следует использовать ни extends
, ни super
.
Предположим, у вас есть метод, который принимает в качестве своего параметра набор объектов, но вы хотите, чтобы он был более гибким, чем просто принятие Collection<Thing>
.
Пример 1: вы хотите просмотреть коллекцию и что-то сделать с каждым элементом.
Тогда список представляет собой producer , поэтому вам следует использовать Collection<? extends Thing>
.
Причина в том, что a Collection<? extends Thing>
может содержать любой подтип Thing
, и, таким образом, каждый элемент будет вести себя как a Thing
при выполнении вашей операции. (На самом деле вы не можете добавить ничего (кроме null) в Collection<? extends Thing>
, потому что вы не можете знать во время выполнения, какой конкретный подтип Thing
содержит коллекция.)
Пример 2: вы хотите добавить что-то в коллекцию.
Тогда список является consumer , поэтому вам следует использовать Collection<? super Thing>
.
Причина здесь в том, что в отличие от Collection<? extends Thing>
, Collection<? super Thing>
всегда может содержать Thing
независимо от того, каков фактический параметризованный тип. Здесь вам все равно, что уже есть в списке, пока это позволяет добавить Thing
; это то, что ? super Thing
гарантирует.
Ответ 2
Принципы, лежащие в основе этого, в информатике называются
- Ковариация:
? extends MyClass
, - Контравариантность:
? super MyClass
и - Инвариантность / недифференцированность:
MyClass
Картинка ниже должна пояснять концепцию. Картинка предоставлена: Андреем Тюкиным
Ответ 3
PECS - это мнемоническое устройство в Java generics, которое помогает нам запомнить, какое ключевое слово использовать при указании границ параметров универсального типа.
1 Производитель расширяет:
- Применяется к возвращаемым типам и переменным с верхними границами.
- Представляет собой производителя значений.
- Позволяет возвращать более конкретный подтип объявленного типа (ковариантный).
- Ковариация - это иерархическое отношение, т.е. отношение "есть-а": Кошка как "есть-а" Животное.
2 Consumer Super:
- Применяется к входным параметрам и переменным с нижними границами.
- Представляет потребителя значений.
- Позволяет принять более общий супертип объявленного типа (контравариантный).
- Контравариантность Изменения отношения подтипов: Animal как "can hold" Cat.
3. Инвариантные / невариантные типы
- (без ограничений) имеют фиксированные отношения и не извлекают выгоду из PECS.
- Фиксированное соотношение, т. е. тип должен оставаться точно таким, как указано, без какой-либо гибкости в принятии подтипов или супертипов: т. е. Где фиксированы как входные, так и выходные типы.
Принцип подстановки Лискова (LSP) гласит, что “объекты в программе должны заменяться экземплярами их подтипов без изменения корректности этой программы”.
ограниченный (т. е. Направляющийся куда-то) подстановочный знак: Существует 3 разных варианта подстановочных знаков:
- Совместное изменение:
? extends T
(ПравлениеT
потомков) - подстановочный знак с верхней границей.T
это самый верхний класс в иерархии наследования. Используйтеextends
подстановочный знак, когда вы только получаете значения из структуры. Верхние границы способствуют безопасной замене подтипов.
Например, если T расширяет Animal , T может быть самим Animal или любым из его подтипов, таким как Cat, Dog и т.д. , Но это не может быть тип "выше", чем Animal , например Object . - Противопоставление:
? super T
( ЦарствованиеT
предка) - подстановочный знак с нижней границей.T
это самый нижний класс в иерархии наследования. Используйтеsuper
подстановочный знак, когда вы только помещаете значения в структуру. Нижние границы способствуют безопасной замене супертипов. - Разница / недифференцированность:
?
или? extends Object
- Неограниченный подстановочный знак. Он обозначает семейство всех типов. Используйте, когда вы одновременно получаете и помещаете.
Примечание: подстановочный знак ?
означает ноль или один раз, представляет неизвестный тип. Подстановочный знак может использоваться как тип параметра, никогда не используемый в качестве аргумента типа при вызове универсального метода, создании экземпляра универсального класса. (т. Е. При использовании подстановочного знака, который не используется нигде в программе, как мы используем T
)
import java.util.ArrayList;
import java.util.List;
class Shape { void draw() {}}
class Circle extends Shape {void draw() {}}
class Square extends Shape {void draw() {}}
class Rectangle extends Shape {void draw() {}}
public class Test {
public static void main(String[] args) {
//? extends Shape i.e. can use any sub type of Shape, here Shape is Upper Bound in inheritance hierarchy
List<? extends Shape> intList5 = new ArrayList<Shape>();
List<? extends Shape> intList6 = new ArrayList<Cricle>();
List<? extends Shape> intList7 = new ArrayList<Rectangle>();
List<? extends Shape> intList9 = new ArrayList<Object>();//ERROR.
//? super Shape i.e. can use any super type of Shape, here Shape is Lower Bound in inheritance hierarchy
List<? super Shape> inList5 = new ArrayList<Shape>();
List<? super Shape> inList6 = new ArrayList<Object>();
List<? super Shape> inList7 = new ArrayList<Circle>(); //ERROR.
//-----------------------------------------------------------
Circle circle = new Circle();
Shape shape = circle; // OK. Circle IS-A Shape
List<Circle> circles = new ArrayList<>();
List<Shape> shapes = circles; // ERROR. List<Circle> is not subtype of List<Shape> even when Circle IS-A Shape
List<? extends Circle> circles2 = new ArrayList<>();
List<? extends Shape> shapes2 = circles2; // OK. List<? extends Circle> is subtype of List<? extends Shape>
//-----------------------------------------------------------
Shape shape2 = new Shape();
Circle circle2= (Circle) shape2; // OK. with type casting
List<Shape> shapes3 = new ArrayList<>();
List<Circle> circles3 = shapes3; //ERROR. List<Circle> is not subtype of List<Shape> even Circle is subetype of Shape
List<? super Shape> shapes4 = new ArrayList<>();
List<? super Circle> circles4 = shapes4; //OK.
}
/*
* Example for an upper bound wildcard (Get values i.e Producer `extends`)
*
* */
public void testCoVariance(List<? extends Shape> list) {
list.add(new Object());//ERROR
list.add(new Shape()); //ERROR
list.add(new Circle()); // ERROR
list.add(new Square()); // ERROR
list.add(new Rectangle()); // ERROR
Shape shape= list.get(0);//OK so list act as produces only
/*
* You can't add a Shape,Circle,Square,Rectangle to a List<? extends Shape>
* You can get an object and know that it will be an Shape
*/
}
/*
* Example for a lower bound wildcard (Put values i.e Consumer`super`)
* */
public void testContraVariance(List<? super Shape> list) {
list.add(new Object());//ERROR
list.add(new Shape());//OK
list.add(new Circle());//OK
list.add(new Square());//OK
list.add(new Rectangle());//OK
Shape shape= list.get(0); // ERROR. Type mismatch, so list acts only as consumer
Object object= list.get(0); //OK gets an object, but we don't know what kind of Object it is.
/*
* You can add a Shape,Circle,Square,Rectangle to a List<? super Shape>
* You can't get an Shape(but can get Object) and don't know what kind of Shape it is.
*/
}
}
Ответ 4
public class Test {
public class A {}
public class B extends A {}
public class C extends B {}
public void testCoVariance(List<? extends B> myBlist) {
B b = new B();
C c = new C();
myBlist.add(b); // does not compile
myBlist.add(c); // does not compile
A a = myBlist.get(0);
}
public void testContraVariance(List<? super B> myBlist) {
B b = new B();
C c = new C();
myBlist.add(b);
myBlist.add(c);
A a = myBlist.get(0); // does not compile
}
}