Я наткнулся на PECS (сокращение от Producer extends
и Consumer super
), когда читал о дженериках.
Может ли кто-нибудь объяснить мне, как использовать PECS для устранения путаницы между extends
и super
?
Я наткнулся на PECS (сокращение от Producer extends
и Consumer super
), когда читал о дженериках.
Может ли кто-нибудь объяснить мне, как использовать PECS для устранения путаницы между extends
и super
?
tl; dr: PECS с точки зрения коллекции. Если вы только извлекаете элементы из общей коллекции, это производитель, и вам следует использовать extends
; если вы только набиваете элементы, это потребитель, и вам следует использовать super
. Если вы делаете и то, и другое с одной и той же коллекцией, вам не следует использовать ни extends
, ни super
.
Предположим, у вас есть метод, который принимает в качестве параметра набор вещей, но вы хотите, чтобы он был более гибким, чем просто принятие Collection<Thing>
.
Случай 1. Вы хотите просмотреть коллекцию и выполнить действия с каждым элементом.
Тогда список является производителем, поэтому вам следует использовать Collection<? extends Thing>
.
Причина в том, что Collection<? extends Thing>
может содержать любой подтип Thing
, и поэтому каждый элемент будет вести себя как Thing
, когда вы выполняете свою операцию. (На самом деле вы не можете ничего добавить (кроме нуля) к Collection<? extends Thing>
, потому что во время выполнения вы не можете узнать, какой конкретный подтип Thing
содержится в коллекции.)
Случай 2: вы хотите добавить объекты в коллекцию.
Тогда список является потребителем, поэтому вам следует использовать Collection<? super Thing>
.
Причина здесь в том, что в отличие от Collection<? extends Thing>
, Collection<? super Thing>
всегда может содержать Thing
независимо от фактического параметризованного типа. Здесь вам все равно, что уже есть в списке, если это позволяет добавить Thing
; это то, что ? super Thing
гарантирует.
doSomethingWithList(List list)
, вы потребляете список, поэтому вам потребуется ковариация / расширение (или инвариантный список). С другой стороны, если ваш метод List doSomethingProvidingList
, тогда вы создаете список, и вам потребуется контравариантность / супер (или инвариантный список).
- person Raman; 24.01.2014
const
в качестве параметров метода в C ++, чтобы указать, что метод не изменяет аргументы?
- person Chatterjee; 24.05.2014
Collection<Thing>
. Причина этого в том, что вы должны быть уверены, что метод get вернет то, что вы можете преобразовать в Thing
, а метод add принимает Thing
, который он может преобразовать в фактический тип элемента. Единственный тип, который удовлетворяет обоим критериям, - это сам Thing
.
- person biziclop; 18.05.2015
Thing
.
- person biziclop; 18.05.2015
Collection <? super Thing>
тоже можно повторять, верно?
- person Eric Zhang; 30.05.2017
T
в конце границы. Например: <? super T>
включает T
и все его супертипы, и аналогично <? extends T>
включает T
и все его подтипы. Верхняя и нижняя границы несовместимы в одной и той же коллекции - одна описывает все типы, включая и ниже объявленного типа, а другая - все типы выше и включая объявленный тип. Итак, изюминка: для Collection
, который должен и производить, и потреблять T
, объявление должно быть просто Collection<T>
- person mouselabs; 17.04.2020
Принципы, лежащие в основе этого в информатике, называются
? extends MyClass
,? super MyClass
иMyClass
Изображение ниже должно объяснить концепцию. Изображение предоставлено: Андрей Тюкин
<? super X>
полезен и что произойдет, если вы его опустите: Java 8 Comparator comparing()
статическая функция < / а>.
- person Andrey Tyukin; 11.08.2018
PECS (производитель extends
и потребитель super
)
мнемоника → Получить и положить принцип.
Этот принцип гласит:
extends
, когда вы получаете значения только из структуры.super
, когда вы помещаете только значения в структуру.Пример на Java:
class Super {
Number testCoVariance() {
return null;
}
void testContraVariance(Number parameter) {
}
}
class Sub extends Super {
@Override
Integer testCoVariance() {
return null;
} //compiles successfully i.e. return type is don't care(Integer is subtype of Number)
@Override
void testContraVariance(Integer parameter) {
} //doesn't support even though Integer is subtype of Number
}
Принцип подстановки Лискова (LSP) гласит, что «объекты в программе должны заменяться экземплярами их подтипов без изменения правильности этой программы».
В системе типов языка программирования правило типизации
Ковариация и контравариантность
Чтобы проиллюстрировать это общее явление, рассмотрим тип массива. Для типа Animal мы можем сделать тип Animal []
Примеры Java:
Object name= new String("prem"); //works
List<Number> numbers = new ArrayList<Integer>();//gets compile time error
Integer[] myInts = {1,2,3,4};
Number[] myNumber = myInts;
myNumber[0] = 3.14; //attempt of heap pollution i.e. at runtime gets java.lang.ArrayStoreException: java.lang.Double(we can fool compiler but not run-time)
List<String> list=new ArrayList<>();
list.add("prem");
List<Object> listObject=list; //Type mismatch: cannot convert from List<String> to List<Object> at Compiletime
ограниченный (т. е. направление куда-то) подстановочный знак: существует 3 разных вида подстановочных знаков:
?
или ? extends Object
- Неограниченный подстановочный знак. Это символ семьи всех типов. Используйте, когда вы и достаете, и кладете.? extends T
(Правление T
потомков) - подстановочный знак с верхней границей. T
- это самый верхний класс в иерархии наследования. Используйте подстановочный знак extends
, когда вы только получаете значения из структуры.? super T
(Правление T
предка) - подстановочный знак с нижней границей. T
- это самый нижний класс в иерархии наследования. Используйте подстановочный знак super
, если в структуру вы только помещаете значения.Примечание. Подстановочный знак ?
означает ноль или один раз, представляет неизвестный тип. Подстановочный знак может использоваться как тип параметра, никогда не использоваться как аргумент типа для вызова универсального метода, создания экземпляра универсального класса (т. Е. Когда используется подстановочный знак, ссылка на который не используется где-либо еще в программе, как мы используем 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.
*/
}
}
Ковариация и контравариантность определяют совместимость на основе по типам. В любом случае дисперсия - это направленное отношение. Ковариация может быть переведена как разные в одном направлении или с разными, тогда как контравариантность означает разные в противоположном направлении или против разных. Ковариантный и контравариантный типы - это не одно и то же, но между ними существует взаимосвязь. Имена подразумевают направление корреляции.
https://stackoverflow.com/a/54576828/1697099
https://stackoverflow.com/a/64888058/1697099
In-variance/Non-variance: ? or ? extends Object - Unbounded Wildcard. It stands for the family of all types. Use when you both get and put.
, я не могу добавить элемент в Список ‹?› Или Список ‹? extends Object ›, поэтому я не понимаю, почему это может быть Use when you both get and put
.
- person LiuWenbin_NO.; 17.05.2019
?
- неограниченный подстановочный знак - соответствует полной противоположности инвариантности. См. Следующую документацию: docs.oracle.com/javase/tutorial / java / generics /, в котором говорится: В случае, когда коду требуется доступ к переменной как в качестве входной, так и выходной переменной, не используйте подстановочный знак. (Они используются в и out как синоним "получить и положить"). За исключением null
, вы не можете добавить в Коллекцию, параметризованную с помощью ?
.
- person mouselabs; 16.04.2020
Вкратце, три простых правила, которые нужно запомнить PECS:
<? extends T>
, если вам нужно получить объект типа T
из коллекции.<? super T>
, если вам нужно поместить объекты типа T
в коллекцию.Как я объясняю в моем ответе на другой вопрос, PECS - это мнемоническое устройство, созданное Джошем Блохом, чтобы помочь запомнить P разработчик extends
, C потребителя super
.
Это означает, что когда параметризованный тип, передаваемый методу, создает экземпляры
T
(они будут извлечены из него каким-либо образом), следует использовать? extends T
, поскольку любой экземпляр подклассаT
является такжеT
.Когда параметризованный тип, передаваемый методу, будет потреблять экземпляры
T
(они будут переданы ему для выполнения каких-либо действий), следует использовать? super T
, потому что экземплярT
может быть законно передан любому методу, который принимает некоторый супертипT
. Например,Comparator<Number>
можно использовать наCollection<Integer>
.? extends T
не будет работать, потому чтоComparator<Integer>
не может работать сCollection<Number>
.
Обратите внимание, что обычно вы должны использовать только ? extends T
и ? super T
для параметров какого-либо метода. Методы должны просто использовать T
в качестве параметра типа для универсального возвращаемого типа.
sort(List<T>,Comparator<? super T>)
объявляет границы типа, и в этом sort
методе компаратор использует T
экземпляры.
- person Holger; 08.06.2021
предположим эту иерархию:
class Creature{}// X
class Animal extends Creature{}// Y
class Fish extends Animal{}// Z
class Shark extends Fish{}// A
class HammerSkark extends Shark{}// B
class DeadHammerShark extends HammerSkark{}// C
Давайте проясним PE - Производитель расширяется:
List<? extends Shark> sharks = new ArrayList<>();
Почему в этот список нельзя добавлять объекты, расширяющие «Акулу»? нравиться:
sharks.add(new HammerShark());//will result in compilation error
Поскольку у вас есть список, который может быть типа A, B или C во время выполнения, вы не можете добавить в него какой-либо объект типа A, B или C, потому что вы можете получить комбинацию, которая не является разрешено в java.
На практике компилятор действительно может видеть во время компиляции, что вы добавляете B:
sharks.add(new HammerShark());
... но у него нет способа определить, будет ли во время выполнения ваш B подтипом или супертипом типа списка. Во время выполнения тип списка может быть любым из типов A, B, C. Таким образом, вы не можете добавить HammerSkark (супертип), например, в список DeadHammerShark.
* Вы скажете: «Хорошо, но почему я не могу добавить в него HammerSkark, ведь это самый маленький тип?». Ответ: Это самый маленький из известных вам. Но HammerSkark может быть расширен кем-то другим, и вы попадете в тот же сценарий.
Давайте проясним CS - Consumer Super:
В той же иерархии мы можем попробовать это:
List<? super Shark> sharks = new ArrayList<>();
Что и почему вы можете добавить в этот список?
sharks.add(new Shark());
sharks.add(new DeadHammerShark());
sharks.add(new HammerSkark());
Вы можете добавить объекты вышеупомянутых типов, потому что все, что ниже акулы (A, B, C), всегда будет подтипом всего, что находится выше акулы (X, Y, Z). Легко понять.
Вы не можете добавлять типы выше Shark, потому что во время выполнения тип добавляемого объекта может быть выше по иерархии, чем объявленный тип списка (X, Y, Z). Это не разрешено.
Но почему вы не можете читать из этого списка? (Я имею в виду, что вы можете получить из него элемент, но вы не можете назначить его ничему, кроме Object o):
Object o;
o = sharks.get(2);// only assignment that works
Animal s;
s = sharks.get(2);//doen't work
Во время выполнения тип списка может быть любым типом выше A: X, Y, Z, ... Компилятор может скомпилировать ваш оператор присваивания (что кажется правильным), но во время выполнения тип s (Животное) может быть ниже по иерархии, чем объявленный тип списка (это может быть Существо или выше). Это не разрешено.
Подводя итоги
Мы используем <? super T>
для добавления объектов типов, равных или ниже T
к List
. Мы не можем читать из него.
Мы используем <? extends T>
для чтения объектов типов, равных или ниже T
из списка. Мы не можем добавить к нему элемент.
Это самый ясный и простой способ подумать о расширениях и супер:
extends
предназначен для чтения
super
предназначен для письма
Я считаю, что «PECS» - неочевидный способ думать о вещах, касающихся того, кто является «производителем», а кто «потребителем». "PECS" определяется с точки зрения самой коллекции данных - коллекция "потребляет", если в нее записываются объекты (она потребляет объекты из вызывающего кода), и он «производит», если объекты считываются из (он создает объекты для некоторого вызывающего кода). Это противоречит тому, как все остальное названо. Стандартные API Java именуются с точки зрения вызывающего кода, а не самой коллекции. Например, ориентированный на коллекцию вид java.util. В списке должен быть метод с именем «receive ()» вместо «add ()» - в конце концов, вызывающий код добавляет элемент, но сам список получает em> элемент.
Я думаю, что более интуитивно, естественно и последовательно думать о вещах с точки зрения кода, который взаимодействует с коллекцией - «читает» ли код или «записывает» в коллекцию? После этого любой код, записывающий в коллекцию, будет «производителем», а любой код, считывающий коллекцию, будет «потребителем».
src
и dst
. Итак, вы имеете дело и с кодом, и с контейнерами одновременно, и в итоге я подумал об этом в соответствии с этими принципами - потребление кода из производящего контейнера и создание кода для потребляющего контейнера.
- person mouselabs; 17.04.2020
(добавление ответа, потому что никогда не бывает достаточно примеров с подстановочными знаками Generics)
// Source
List<Integer> intList = Arrays.asList(1,2,3);
List<Double> doubleList = Arrays.asList(2.78,3.14);
List<Number> numList = Arrays.asList(1,2,2.78,3.14,5);
// Destination
List<Integer> intList2 = new ArrayList<>();
List<Double> doublesList2 = new ArrayList<>();
List<Number> numList2 = new ArrayList<>();
// Works
copyElements1(intList,intList2); // from int to int
copyElements1(doubleList,doublesList2); // from double to double
static <T> void copyElements1(Collection<T> src, Collection<T> dest) {
for(T n : src){
dest.add(n);
}
}
// Let's try to copy intList to its supertype
copyElements1(intList,numList2); // error, method signature just says "T"
// and here the compiler is given
// two types: Integer and Number,
// so which one shall it be?
// PECS to the rescue!
copyElements2(intList,numList2); // possible
// copy Integer (? extends T) to its supertype (Number is super of Integer)
private static <T> void copyElements2(Collection<? extends T> src,
Collection<? super T> dest) {
for(T n : src){
dest.add(n);
}
}
«Правило» PECS просто гарантирует, что следующее является законным:
?
, он может юридически относиться к T
?
, на него может ссылаться T
Типичное сочетание строк List<? extends T> producer, List<? super T> consumer
просто гарантирует, что компилятор может применять стандартные правила отношения наследования «IS-A». Если бы мы могли сделать это на законных основаниях, было бы проще сказать <T extends ?>, <? extends T>
(или еще лучше в Scala, как вы можете видеть выше, это [-T], [+T]
. К сожалению, лучшее, что мы можем сделать, это <? super T>, <? extends T>
.
Когда я впервые столкнулся с этим и сломал это в своей голове, механика имела смысл, но сам код продолжал сбивать меня с толку - я продолжал думать: «Похоже, что границы не нужно так инвертировать» - хотя я было ясно из вышеизложенного - что речь идет просто о гарантии соблюдения стандартных правил использования.
Мне помогло то, что я посмотрел на это, используя обычное назначение в качестве аналогии.
Рассмотрим следующий код игрушки (не готовый к производству):
// copies the elements of 'producer' into 'consumer'
static <T> void copy(List<? extends T> producer, List<? super T> consumer) {
for(T t : producer)
consumer.add(t);
}
Иллюстрируя это с точки зрения аналогии с присваиванием, для consumer
подстановочный знак ?
(неизвестный тип) является ссылкой - «левая сторона» присвоения - и <? super T>
гарантирует, что независимо от того, что ?
, T
"IS-A" ?
- это T
может быть назначен ему, потому что ?
является супертипом (или, самое большее, тем же типом), что и T
.
Для producer
проблема такая же, она просто инвертирована: подстановочный знак producer
?
(неизвестный тип) - это референт - "правая часть" присвоения - и <? extends T>
гарантирует, что независимо от того, что ?
, ?
"ЕСТЬ -A "T
- это это может быть назначено на T
, потому что ?
является подтипом (или, по крайней мере, тем же типом), что и T
.
class A
и class B
, для каждого из которых определен единственный общедоступный метод - a()
и b()
- соответственно. Если B extends A
, то результат таков, что B
содержит как a()
, так и b()
. B
, затем IS-A A
, потому что он полностью представляет интерфейс A
. Но то же самое нельзя сказать о A
- A
не B
, мы знаем только, что B
(по крайней мере) A
, так как extends A
- person mouselabs; 19.06.2020
давайте попробуем визуализировать эту концепцию.
<? super SomeType>
является «неопределенным (пока)» типом, но этот неопределенный тип должен быть суперклассом класса SomeType.
То же самое и с <? extends SomeType>
. Это тип, который должен расширять класс SomeType (он должен быть дочерним классом класса SomeType).
Если мы рассмотрим концепцию «наследования классов» на диаграмме Венна, пример будет таким:
Класс Mammal расширяет класс Animal (класс Animal является суперклассом класса Mammal).
Класс Cat / Dog расширяет класс Mammal (класс Mammal - это супер класс класса Cat / Dog).
Затем давайте представим «круги» на приведенной выше диаграмме как «коробку», имеющую физический объем.
НЕЛЬЗЯ поместить большую коробку в меньшую.
Вы можете вставлять ТОЛЬКО меньшую коробку в большую.
Когда вы говорите <? super SomeType>
, вы хотите описать "коробку" того же размера или больше, чем поле SomeType.
Если вы говорите <? extends SomeType>
, то вы хотите описать "поле" того же размера или меньше, чем поле SomeType.
Так что же такое PECS?
Примером «Продюсера» является Список, из которого мы только читаем.
Примером «Потребителя» является Список, в который мы только записываем.
Имейте в виду следующее:
Мы «читаем» от «продюсера» и берем этот материал в свою коробку.
И мы «вписываем» свою коробку в «потребителя».
Итак, нам нужно прочитать (взять) что-то от «производителя» и поместить это в нашу «коробку». Это означает, что любые коробки, взятые у производителя, НЕ больше, чем наша «коробка». Вот почему "P roducer E xtends".
«Расширяется» означает меньшую рамку (меньший кружок на диаграмме Венна выше). Ящики производителя должны быть меньше, чем наша собственная, потому что мы заберем эти ящики у производителя и поместим их в свою. Ничего большего, чем наша коробка, поставить нельзя!
Кроме того, нам нужно написать (поместить) нашу собственную «коробку» в «потребителя». Это означает, что коробки потребителя НЕ должны быть меньше, чем наша собственная. Вот почему «C потребителя S uper».
«Супер» означает большую рамку (больший кружок на диаграмме Венна выше). Если мы хотим поместить наши собственные коробки в потребителя, коробки потребителя должны быть больше, чем наша коробка!
Теперь мы можем легко понять этот пример:
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));
}
}
В приведенном выше примере мы хотим прочитать (взять) что-то из src
и записать (поместить) это в dest
. Итак, src
- это «Производитель», и его «коробки» должны быть меньше (точнее), чем какой-то тип T
.
И наоборот, dest
является «Потребителем», и его «коробки» должны быть больше (более общие), чем какой-либо тип T
.
Если бы «коробки» у src
были больше, чем у dest
, мы не могли бы поместить эти большие коробки в меньшие коробки, которые есть у dest
.
Если кто-нибудь прочитает это, я надеюсь, что это поможет вам лучше понять «P roducer E xtends, C onsumer S uper . »
Удачного кодирования! :)
Ковариация: принять подтипы
Контравариантность: принять супертипы
Ковариантные типы доступны только для чтения, а контравариантные типы доступны только для записи.
Помните это:
Потребитель ест ужин (супер); Производитель расширяет фабрику своих родителей
[Ковариация и контравариантность]
Давайте посмотрим на пример
public class A { }
//B is A
public class B extends A { }
//C is A
public class C extends A { }
Generics позволяет безопасно работать с типами динамически
//ListA
List<A> listA = new ArrayList<A>();
//add
listA.add(new A());
listA.add(new B());
listA.add(new C());
//get
A a0 = listA.get(0);
A a1 = listA.get(1);
A a2 = listA.get(2);
//ListB
List<B> listB = new ArrayList<B>();
//add
listB.add(new B());
//get
B b0 = listB.get(0);
Поскольку Коллекция Java является ссылочным типом, в результате возникают следующие проблемы:
Проблема №1
//not compiled
//danger of **adding** non-B objects using listA reference
listA = listB;
* Общий Swift не имеет такой проблемы, потому что Collection - это Value type
[About], поэтому новая коллекция создано
Проблема №2
//not compiled
//danger of **getting** non-B objects using listB reference
listB = listA;
Подстановочный знак - это функция ссылочного типа, и его нельзя создать напрямую.
Решение №1 <? super A>
, также известное как нижняя граница или контравариантность, также известная как потребители, гарантирует, что оно управляется A и всеми суперклассами, поэтому безопасно добавить
List<? super A> listSuperA;
listSuperA = listA;
listSuperA = new ArrayList<Object>();
//add
listSuperA.add(new A());
listSuperA.add(new B());
//get
Object o0 = listSuperA.get(0);
Решение №2
<? extends A>
, также известная как верхняя граница, также известная как ковариация, или производители, гарантирует, что она управляется A и всеми подклассами, поэтому можно безопасно получить и преобразовать
List<? extends A> listExtendsA;
listExtendsA = listA;
listExtendsA = listB;
//get
A a0 = listExtendsA.get(0);
На примере из реальной жизни (с некоторыми упрощениями):
<? super FreightCarSize>
<? extends DepotSize>
? extends B
означает B и все, что расширяет B.
- person asgs; 28.09.2016
super
часть, но дает представление о другой. - person lupchiazoem   schedule 20.01.2019