Что такое PECS (производитель расширяет потребительский супер)?
я наткнулся на Печ (сокращение от производитель extends
и потребителем super
) при чтении дженериков.
может кто-нибудь объяснить мне, как использовать Печ для устранения путаницы между extends
и super
?
11 ответов
tl; dr: "Печ" с точки зрения коллекции. Если вы только вытягивая предметы из общей коллекции, это производитель, и вы должны использовать 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
гарантии.
принципы, стоящие за этим в информатике, названы в честь
- ковариация - ? расширяет класса MyClass
- контравариантность - ? супер MyClass и
- инвариантность / отсутствие дисперсии-MyClass
изображение ниже должно объяснить концепцию.
Фото предоставлено : Андрей Тюкин
Печ (сокращение от"производитель extends
и потребителем super
") можно объяснить : получить и поставить принцип
принцип Get и Put (из Java-дженериков и коллекций)
в нем говорится,
- использовать расширяет подстановочный знак когда вы только get значения из структуры
- использовать супер подстановочный знак когда вы только поставить значения в a структура
- и не используйте подстановочный знак когда вы и получить и положить.
давайте разберемся на примере:
1. Для расширенного подстановочного знака (получить значения i.E Producer extends
)
вот метод, который принимает коллекцию чисел, преобразует каждый в double
, и суммирует их
public static double sum(Collection<? extends Number> nums) {
double s = 0.0;
for (Number num : nums)
s += num.doubleValue();
return s;
}
назовем метод :
List<Integer>ints = Arrays.asList(1,2,3);
assert sum(ints) == 6.0;
List<Double>doubles = Arrays.asList(2.78,3.14);
assert sum(doubles) == 5.92;
List<Number>nums = Arrays.<Number>asList(1,2,2.78,3.14);
assert sum(nums) == 8.92;
С, sum()
способ использования extends
, все следующие вызовы являются законными.
Первые два вызова не были бы законными, если бы extends не использовался.
исключение вы ничего не могу поставить в тип, объявленный с extends
подстановочный знак-кроме значения null
, который принадлежит к каждому типу ссылки:
List<Integer> ints = new ArrayList<Integer>();
ints.add(1);
ints.add(2);
List<? extends Number> nums = ints;
nums.add(null); // ok
assert nums.toString().equals("[1, 2, null]");
2. Для подстановочного знака Super (put values i.E потребитель super
)
вот метод, который принимает набор чисел и int n
, и ставит первый n
числа, начиная с нуля, в коллекцию:
public static void count(Collection<? super Integer> ints, int n) {
for (int i = 0; i < n; i++) ints.add(i);
}
назовем метод :
List<Integer>ints = new ArrayList<Integer>();
count(ints, 5);
assert ints.toString().equals("[0, 1, 2, 3, 4]");
List<Number>nums = new ArrayList<Number>();
count(nums, 5); nums.add(5.0);
assert nums.toString().equals("[0, 1, 2, 3, 4, 5.0]");
List<Object>objs = new ArrayList<Object>();
count(objs, 5); objs.add("five");
assert objs.toString().equals("[0, 1, 2, 3, 4, five]");
С, count()
способ использования super
, все следующие вызовы являются законными:
Последние два звонка не были бы законными, если бы super не был используемый.
исключение вы не может из типа, объявленного с super
подстановочный знак-кроме значения типа Object
, который является супертипом любого ссылочного типа:
List<Object> objs = Arrays.<Object>asList(1,"two");
List<? super Integer> ints = objs;
String str = "";
for (Object obj : ints) str += obj.toString();
assert str.equals("1two");
3. Когда оба Get и Put, не используйте подстановочный знак
всякий раз, когда вы и значения и вам значения из той же структуры, вы не следует использовать подстановочные.
public static double sumCount(Collection<Number> nums, int n) {
count(nums, n);
return sum(nums);
}
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
}
}
Печ (производитель extends
и потребителем super
)
мнемоника → получить и поставить принцип.
этот принцип гласит, что:
- используйте подстановочный знак extends, когда вы получаете значения только из структуры.
- используйте супер подстановочный знак, когда вы только помещаете значения в структуру.
- и не используйте подстановочный знак, когда вы оба получаете и ставите.
в Java параметры и параметры универсального типа не поддержка наследования следующим образом.
class Super {
void testCoVariance(Object parameter){} // method Consumes the Object
Object testContraVariance(){ return null;} //method Produces the Object
}
class Sub extends Super {
@Override
void testCoVariance(String parameter){} //doesn't support eventhough String is subtype of Object
@Override
String testContraVariance(){ return null;} //compiles successfully i.e. return type is don't care
}
принцип подстановки Лисков: массивы ковариантные(небезопасно), но дженерики не есть invarient(безопасный). т. е. принцип подстановки не работает с параметризованными типами, что означает, что писать незаконно.
Ковариант просто означает if X
является подтипом Y
затем X[]
также будет подтипом Y[]
.
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
подстановочный знак, когда вы только Get значения из структуры. - Contra-дисперсия:
? super T
( семьи всех типов, которые являются супертипамиT
) - подстановочный знак с нижняя граница.T
- это ниже - самый класс в иерархии наследования. Используйтеsuper
подстановочный знак, когда вы только поставить значения в a структура.
Примечание: подстановочный знак ?
означает ноль или один раз, представляет собой неизвестный тип. Подстановочный знак может использоваться как тип параметра, никогда не используемый в качестве аргумента типа для вызова универсального метода, создания экземпляра универсального класса.(т. е. при использовании подстановочного знака, который не используется в другом месте в программе, как мы используем 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 TestContraVariance {
/*
* 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<? extends Shape>
* You can't get an Shape(but can get Object) and don't know what kind of Shape it is.
*/
}
}
как я объясняю в мой ответ: к другому вопросу, PECS-это мнемоническое устройство, созданное Джошем Блохом, чтобы помочь запомнить Producer extends
, Consumer 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
в качестве параметра type для универсального возвращаемого типа.
в двух словах легко запомнить Печ
- использовать
<? extends T>
подстановочные если вам нужно получить объект типT
из коллекции. - использовать
<? super T>
подстановочный знак, если вам нужно поместить объекты типаT
in коллекция. - Если вам нужно удовлетворить обе вещи, ну, не используйте подстановочный знак. Как все очень просто.
(добавление ответа, потому что никогда не хватает примеров с Подстановочными знаками дженериков)
// 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-Producer Extends:
List<? extends Shark> sharks = new ArrayList<>();
почему вы не можете добавить объекты, которые расширяют "акулу" в этом списке? например:
sharks.add(new HammerShark());//will result in compilation error
поскольку у вас есть список, который может быть типа A, B или C во время, вы не можете добавить в него какой-либо объект типа A, B или C, потому что вы можете получить комбинацию, которая не разрешена в java.
на практике, компилятор действительно может видеть в compiletime, что вы добавляете B:
sharks.add(new HammerShark());
...но он не может сказать, будет ли во время выполнения ваш B подтипом или супертипом типа списка. Во время выполнения типом списка может быть любой из типов A, B, C. Поэтому вы не можете добавить HammerSkark (super type) в список DeadHammerShark, например.
* вы скажете: "Хорошо, но почему я не могу добавить в него HammerSkark, так как это самый маленький тип?". Ответ: Это самый маленький вы знаю. Купить hammerskark можно будьте расширены кем-то еще, и вы окажетесь в том же сценарии.
давайте уточним CS-Consumer Super:
в той же иерархии мы можем попробовать это:
List<? super Shark> sharks = new ArrayList<>();
что и почему ты can добавить в этот список?
sharks.add(new Shark());
sharks.add(new DeadHammerShark());
sharks.add(new HammerSkark());
вы можете добавить вышеуказанные типы объектов, потому что все,что ниже shark(A,B,C) всегда будет подтипами чего-либо выше shark (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 из списка. мы не можем добавить элемент к нему.
подстановочные знаки можно использовать тремя способами:
- Upper bound Wildcard ( ? extends Type ). - Lower bound Wildcard ( ? super Type ) . - Unbounded Wildcard ( ? ) .
для целей этого обсуждения полезно думать о переменных как об одной из двух функций:
- In Variable An "in" variable serves up data to the code. Imagine a copy method with two arguments: copy(src, dest) The src argument provides the data to be copied, so it is the "in" parameter. - Out Variable An "out" variable holds data for use elsewhere. In the copy example, copy(src, dest) the dest argument accepts data, so it is the "out" parameter. An "in" variable is defined with an upper bounded wildcard, using the extends keyword. An "out" variable is defined with a lower bounded wildcard, using the super keyword. In the case where the "in" variable can be accessed using methods defined in the Object class, use an unbounded wildcard. In the case where the code needs to access the variable as both an "in" and an "out" variable, do not use a wildcard. class NaturalNumber { private int i; public NaturalNumber(int i) { this.i = i; } } class EvenNumber extends NaturalNumber { public EvenNumber(int i) { super(i); } } Consider the following code: List<EvenNumber> le = new ArrayList<>(); List<? extends NaturalNumber> ln = le; ln.add(new NaturalNumber(35)); // compile-time error You can add null. You can invoke clear. You can get the iterator and invoke remove. You can capture the wildcard and write elements that you've read from the list.