Что такое PEC (продюсер расширяет потребитель Super)?
-
01-10-2019 - |
Вопрос
Я вошел через УИК (короткие для Режиссер extends
и потребитель super
) во время чтения на дженерах.
Может ли кто-нибудь объяснить мне, как использовать PECS для разрешения путаницы между extends
а также super
?
Решение
TL; DR: «PEC» - с точки зрения сбора. Если ты Только Вытягивание предметов из общей коллекции, это производитель, и вы должны использовать 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
гарантии.
Другие советы
Принципы этого в информатике называются
- Ковариация:
? extends MyClass
, - Контравариация:
? super MyClass
а также - Инвариантность / Неприемность:
MyClass
На рисунке ниже следует объяснить концепцию. Картина любезностей: Андрей Тюкин
PECS (производитель extends
и потребитель super
)
Мнонический → Получить и поставить принцип.
Этот принцип утверждает, что:
- Используйте продление подстановки, когда вы получаете только значения из структуры.
- Используйте супер подстановочный знак, когда вы устанавливаете только значения в структуру.
- И не используйте подстановочный знак, когда вы оба получаете и поставили.
Пример в Java:
class Super {
Object testCoVariance(){ return null;} //Covariance of return types in the subtype.
void testContraVariance(Object parameter){} // Contravariance of method arguments in the subtype.
}
class Sub extends Super {
@Override
String testCoVariance(){ return null;} //compiles successfully i.e. return type is don't care(String is subtype of Object)
@Override
void testContraVariance(String parameter){} //doesn't support even though String is subtype of Object
}
Принцип замены Liskov: Если S - подтип т, то объекты типа T могут быть заменены объектами типа S.
В системе типа языка программирования, правило набора текста
- ковариант Если он сохраняет упорядочение типов (≤), которые типы заказов от более конкретного для более общего;
- контрапарит Если это изменяет этот заказ;
- инвариант или неверенн, если ни одно из них не применяется.
- Типы данных только для чтения (источники) могут быть ковариант;
- Типы данных только для записи (мойки) могут быть контрапарит.
- Соребительные типы данных, которые действуют как источники, так и мойки, должны быть инвариант.
Чтобы проиллюстрировать это общее явление, рассмотрите тип массива. Для типа животного мы можем сделать тип животного [
- ковариант: кошка [] - животное [];
- контрапарит: животное [] - кошка [];
- инвариант: животное [] не является кошкой [], а кошка [] не является животным [].
Примеры 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 разных аромата подстановочных знаков:
- In-variace / Неприемник:
?
или? extends Object
- Неограниченный Подстановочный знак. Это означает семью всех типов. Используйте, когда вы оба получите и поставьте. - Ко-дисперсия:
? extends T
(семья всех типов, которые являются подтипамиT
) - подстановочный знак с верхняя граница.T
это верх-мический класс в иерархии наследования. Использовать АНextends
подстановочный знак, когда вы только Получать значения из структуры. - Contra-variance:
? super T
(семья всех типов, которые являются супертипамиT
) - подстановочный знак с нижняя граница.T
это ниже-мический класс в иерархии наследования. Использоватьsuper
подстановочный знак, когда вы только Помещать значения в структуру.
Примечание: подстановочный знак ?
означает ноль или один раз, представляет собой неизвестный тип. Подстановочный знак можно использовать в качестве типа параметра, который никогда не используется в качестве аргумента типа для генерального вызова метода, создания экземпляра общего класса. (Т.е. когда используется подстановочный знак, который ссылается, что не используется в других странах, например, T
)
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 {
/*
* Example for an upper bound wildcard (Get values i.e Producer `extends`)
*
* */
public void testCoVariance(List<? extends Shape> list) {
list.add(new Shape()); // Error: is not applicable for the arguments (Shape) i.e. inheritance is not supporting
list.add(new Circle()); // Error: is not applicable for the arguments (Circle) i.e. inheritance is not supporting
list.add(new Square()); // Error: is not applicable for the arguments (Square) i.e. inheritance is not supporting
list.add(new Rectangle()); // Error: is not applicable for the arguments (Rectangle) i.e. inheritance is not supporting
Shape shape= list.get(0);//compiles 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 Shape());//compiles i.e. inheritance is supporting
list.add(new Circle());//compiles i.e. inheritance is supporting
list.add(new Square());//compiles i.e. inheritance is supporting
list.add(new Rectangle());//compiles i.e. inheritance is supporting
Shape shape= list.get(0); // Error: Type mismatch, so list acts only as consumer
Object object= list.get(0); // 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.
*/
}
}
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
}
}
Как я объясню в мой ответ К другому вопросу, PECS - это мнемоническое устройство, созданное Джошем Блохом, чтобы помочь вспомнить продущик свидетельствоватьxtends
, Слияниенавес сuper
.
Это означает, что при передаче параметризованного типа к методу будет производить экземпляры
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
В качестве параметра типа на универсальном типе возврата.
В двух словах, три легких правила, чтобы помнить PECS:
- Использовать
<? extends T>
Подстановочный знак Если вам нужно получить объект типаT
из коллекции. - Использовать
<? super T>
Подстановочный знак Если вам нужно поставить объекты типаT
в коллекции. - Если вам нужно удовлетворить оба вещи, хорошо, не используйте никакой подстановки. Так просто, как, что.
(Добавление ответа, потому что никогда не достаточно примеров с драковыми подстановочными картами)
// 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);
}
}
Давайте предположим эту иерархию:
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 будет подтип или Supertype типа списка. При выполнении типа списка может быть любой из типов A, B, C. Таким образом, вы не можете в конечном итоге добавить Hammerskark (Super Type) в списке Deadhammershark, например.
* Вы скажете: «Хорошо, но почему я не могу добавить Hammerskark в нем, так как это самый маленький тип?». Ответ: это самый маленький ты знать. Но Hammerskark также может быть продлен кем-то другим, и вы в конечном итоге в том же сценарии.
Давайте уточним CS - потребитель Супер:
В той же иерархии мы можем попробовать это:
List<? super Shark> sharks = new ArrayList<>();
Что и почему ты могу Добавить в этот список?
sharks.add(new Shark());
sharks.add(new DeadHammerShark());
sharks.add(new HammerSkark());
Вы можете добавить вышеуказанные типы объектов, потому что все ниже акулы (A, B, C) всегда будут подтипы чего-либо выше, выше акулы (X, Y, Z). Легко понять.
Ты не можем Добавить типы выше акулы, потому что во время выполнения Тип добавленного объекта может быть выше в иерархии, чем заявленный тип списка (x, y, z). Это не допускается.
Но почему вы не можете прочитать из этого списка? (Я имею в виду, что вы можете получить элемент из этого, но вы не можете назначить его ничего, кроме объекта 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 в списке. Мы не можем прочитать из этого.
Мы используем <? extends T>
Чтобы прочитать объекты типов, равных или ниже t из списка. Мы не можем добавить элемент к нему.
Помните это:
Потребитель ест ужин(супер); Режиссер расширяться его родительский завод
Ковариация: принять подтипы
Контравариация: принять супертипс
Ковариантные типы являются только для чтения, а контравариантные типы являются только для записи.
Использование реального примера жизни (с некоторыми упрощениями):
- Представьте себе грузовой поезд с грузовыми автомобилями как аналогии с списком.
- Ты сможешь помещать груз в грузовом автомобиле, если груз имеет такой же или меньший размер чем грузовой автомобиль =
<? super FreightCarSize>
- Ты сможешь разгружать груз от грузовой машины, если у вас есть Достаточно места (больше, чем размер груза) в вашем депо =
<? extends DepotSize>