Відповіді:
tl; dr: "PECS" - з точки зору колекції. Якщо ви лише витягуєте предмети із загальної колекції, це виробник, і вам слід користуватися extends
; якщо ви тільки начиняєте речі, це споживач і вам слід користуватися super
. Якщо ви обидва використовуєте одну і ту ж колекцію, ви не повинні використовувати ні те, extends
ні super
.
Припустимо, у вас є метод, який приймає за свій параметр набір речей, але ви хочете, щоб він був більш гнучким, ніж просто прийняття Collection<Thing>
.
Випадок 1: Ви хочете пройти колекцію і зробити речі з кожним предметом.
Тоді цей список є виробником , тому вам слід скористатися Collection<? extends Thing>
.
Аргументація полягає в тому, що a 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
, то ви виробляти цей список і потребуватиме в контраваріаціі / супер (або інваріантне List).
const
параметри методу в C ++ для позначення того, що метод не змінює аргументи?
Принципи, які стоять за цим в інформатиці, називаються
? 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
}
Принцип заміщення Ліскова: якщо S є підтипом T, то об'єкти типу T можуть бути замінені об'єктами типу S.
У системі типів мови програмування діє правило набору тексту
Коваріація та противаріантність
Для ілюстрації цього загального явища розглянемо тип масиву. Для типу 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
обмежений (тобто, спрямований кудись) підстановочний код : Є три різні аромати символів:
?
або ? extends Object
- Без обмеженого підстановки. Це означає сім'ю всіх типів. Використовуйте, коли ви отримуєте і кладете.? extends T
(сімейство всіх типів, що є підтипами T
) - макіяж із верхньою межею . T
це верхня -Велика клас в ієрархії успадкування. Використовуйте extends
підстановку, коли ви отримуєте лише значення зі структури.? super T
(сімейство всіх типів, що є супертипами T
) - макіяж із нижньою межею . T
є найнижчим класом в ієрархії спадкування. Використовуйте super
підстановку, коли ви лише вводите значення в структуру.Примітка: підстановка ?
означає нуль або один раз , являє собою невідомий тип. Підстановочний знак може використовуватися як тип параметра, ніколи не використовується як аргумент типу для виклику загального методу, створення екземпляра загального класу. (Тобто, коли використовується wildcard, що посилання не використовується в іншому місці програми, як ми використовуємо 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.
*/
}
}
In-variance/Non-variance: ? or ? extends Object - Unbounded Wildcard. It stands for the family of all types. Use when you both get and put.
я не можу додати елемент до списку <?> Або списку <? розширює Object>, тому я не розумію, чому це може бути Use when you both get and put
.
?
- "необмежений підмітний знак" - відповідає точно протилежному інваріантності. Будь ласка, зверніться до наступної документації: docs.oracle.com/javase/tutorial/java/generics/… де зазначено: У випадку, коли коду потрібно отримати доступ до змінної як змінної "in", так і "out", зробіть не використовувати підстановку. (Вони використовують "in" та "out" як синоніми "get" та "put"). За винятком null
ви не можете додати до колекції, параметризовані на ?
.
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 B
означає B і все, що продовжує B.
Як я пояснюю в моїй обороні на інше питання, PECS мнемонічне пристрій , створене Джош Блох , щоб допомогти згадати P roducer extends
, C onsumer super
.
Це означає, що коли параметризований тип, який передається методу, буде створювати екземпляри
T
(вони будуть витягнуті з нього певним чином),? extends T
слід використовувати, оскільки будь-який примірник підкласуT
також є аT
.Коли параметризований тип, що передається методу, буде споживати екземпляри
T
(вони будуть передані йому, щоб зробити щось),? super T
слід використовувати, тому що екземплярT
юридично може бути переданий будь-якому методу, який приймає деякий супертипT
. Наприклад, AComparator<Number>
може бути використаний наCollection<Integer>
.? extends T
не буде працювати, тому що aComparator<Integer>
не міг працювати на aCollection<Number>
.
Зверніть увагу , що , як правило , ви повинні бути тільки з допомогою ? extends T
і ? super T
для параметрів деякого методу. Методи повинні просто використовувати T
як параметр типу для загального типу повернення.
Коротше кажучи, три простих правила запам'ятати PECS:
<? extends T>
підстановку, якщо вам потрібно отримати об'єкт типу T
з колекції.<? super T>
підстановку, якщо вам потрібно помістити об'єкти типу T
в колекцію.припустимо цю ієрархію:
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
Давайте уточнимо, що ПЕ - виробник розширює:
List<? extends Shark> sharks = new ArrayList<>();
Чому ви не можете додати до цього списку об’єкти, що розширюють "Shark"? подібно до:
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). Це заборонено.
Але чому ви не можете прочитати з цього списку? (Я маю на увазі, що ви можете отримати елемент з нього, але ви не можете призначити його іншому, крім Об'єкта 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 (Animal) може бути нижчим у ієрархія, ніж оголошений тип списку (який може бути Creature або вище). Це заборонено.
Підсумовуючи
Ми використовуємо <? super T>
для додавання об'єктів типів, рівних або нижче, T
до List
. Ми не можемо читати з нього.
Ми використовуємо <? extends T>
для читання об'єктів типів, рівних чи нижчих T
зі списку. Ми не можемо додати елемент до нього.
(додаючи відповідь, тому що ніколи не вистачає прикладів з підказками 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);
}
}
Це найясніший і найпростіший спосіб для мене думати про розширення і супер:
extends
для читання
super
призначений для написання
Я вважаю, що "PECS" є неочевидним способом думати про речі щодо того, хто є "виробником", а хто "споживачем". "PECS" визначається з точки зору самого збору даних - збірник "споживає", якщо до нього записуються об'єкти (він споживає об'єкти з кодів виклику), і "виробляє", якщо з нього читаються об'єкти (це виробляє об'єкти до якогось викликового коду). Це суперечить тому, як все інше названо. Стандартні API Java називаються з точки зору викликового коду, а не самої колекції. Наприклад, вигляд java.util.List, орієнтований на колекцію, повинен мати метод з назвою "prima ()" замість "add ()" - адже,елемент, але сам список отримує елемент.
Я думаю, що інтуїтивніше, природніше та послідовніше думати про речі з точки зору коду, який взаємодіє із колекцією - чи код "читає" чи "записує" до колекції? Після цього будь-який запис коду до колекції буде "виробником", а будь-яке зчитування коду з колекції буде "споживачем".
src
і dst
. Отже, ви маєте справу з кодом і контейнерами одночасно, і я в кінцевому підсумку роздумував над цим - "споживаючий код" споживає з контейнера, що виробляє, а "код коду" виробляє для споживача.
"Правило" 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
«s ?
підстановлювальний (невідомий типу) є референтом -" права рука сторона "поступки - і <? extends T>
гарантує , що все ?
це,?
" IS-A " T
- що він може бути призначений на аT
, тому що ?
це підтип (або принаймні того ж типу) як T
.
Використовуючи приклад із реального життя (з деякими спрощеннями):
<? super FreightCarSize>
<? extends DepotSize>
Коваріація : прийняти підтипи
Контраваріантність : прийняти супертипи
Коваріантні типи є лише для читання, тоді як противаріантні - лише для запису.
Розглянемо приклад
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 немає такої проблеми, оскільки колекція є 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>
aka верхня межа aka коваріація ака виробники гарантує, що вона функціонує за допомогою A та всіх підкласів, тому її безпечно отримувати та передавати
List<? extends A> listExtendsA;
listExtendsA = listA;
listExtendsA = listB;
//get
A a0 = listExtendsA.get(0);
super
частину, але дає уявлення про іншу.