Очень хорошее объяснение с примером @ youtube.com/watch?v=34oiEq9nD0M&feature=youtu.be&t=1630, который объясняет superчасть, но дает представление о другой.
Лупчиазоем
Ответы:
844
tl; dr: «PECS» с точки зрения коллекции. Если вы берете только предметы из общей коллекции, это производитель, и вы должны использовать его extends; если вы только набиваете предметы, это потребитель, и вы должны его использовать super. Если вы делаете оба с одной коллекцией, вы не должны использовать либо extendsили super.
Предположим, у вас есть метод, который принимает в качестве параметра набор вещей, но вы хотите, чтобы он был более гибким, чем просто принятие a Collection<Thing>.
Случай 1: Вы хотите просмотреть коллекцию и сделать что-то с каждым предметом.
Тогда список является производителем , поэтому вы должны использовать Collection<? extends Thing>.
Причина заключается в том, что a Collection<? extends Thing>может содержать любой подтип Thing, и поэтому каждый элемент будет вести себя как a, Thingкогда вы выполняете свою операцию. (На самом деле вы ничего не можете добавить к a Collection<? extends Thing>, потому что вы не можете знать во время выполнения, какой конкретный подтип Thingколлекции содержит.)
Случай 2: Вы хотите добавить вещи в коллекцию.
Тогда список является потребителем , поэтому вы должны использовать Collection<? super Thing>.
Причиной здесь является то Collection<? extends Thing>, что в отличие от этого , Collection<? super Thing>всегда может храниться Thingнезависимо от того, что является фактическим параметризованным типом. Здесь вас не волнует, что уже есть в списке, если это позволит добавить a Thing; это то, что ? super Thingгарантирует.
Я всегда пытаюсь думать об этом так: производителю разрешено производить что-то более конкретное, следовательно, расширяется , потребителю разрешается принимать что-то более общее, а значит, супер .
Feuermurmel
10
Еще один способ запомнить разницу между производителем и потребителем - подумать о сигнатуре метода. Если у вас есть метод doSomethingWithList(List list), вы потребляя список и поэтому нужно будешь ковариация / продолжается (или инвариантное List). С другой стороны , если ваш метод List doSomethingProvidingList, то вы производить этот список и будет нуждаться в контравариации / супер (или инвариантное List).
Раман
3
@MichaelMyers: Почему мы не можем просто использовать параметризованный тип для обоих этих случаев? Есть ли здесь какое-то конкретное преимущество использования подстановочных знаков или это просто средство улучшения читабельности, подобное, скажем, использованию ссылок на constпараметры метода в C ++ для обозначения того, что метод не изменяет аргументы?
Чаттерджи
7
@ Раман, я думаю, ты просто запутался. В doSthWithList (у вас может быть List <? Super Thing>), поскольку вы являетесь потребителем, вы можете использовать super (помните, CS). Тем не менее, это список <? extends Thing> getList (), поскольку вам разрешено возвращать что-то более конкретное при создании (PE).
masterxilo
4
@AZ_ Я разделяю твои чувства. Если метод действительно получает () из списка, метод считается Consumer <T>, а список считается поставщиком; но правило PECS «с точки зрения списка», поэтому требуется «расширяет». Это должно быть GEPS: получить расширяет; поставить супер.
Treefish Чжан
561
Принципы, стоящие за этим в информатике называется
Ковариации: ? extends MyClass,
Контравариантность: ? super MyClassи
Инвариантность / не отклонение: MyClass
Картинка ниже должна объяснить концепцию. Фото предоставлено Андреем Тюкиным
Всем привет. Я Андрей Тюкин, я просто хотел подтвердить, что anoopelias & DaoWen связались со мной и получили мое разрешение на использование скетча, он лицензирован по (CC) -BY-SA. Thx @ Anoop за то, что он дал ему вторую жизнь ^^ @Brian Agnew: (на "несколько голосов"): это потому, что это эскиз для Scala, он использует синтаксис Scala и предполагает дисперсию на сайте объявлений, которая довольно сильно отличается от странного вызова Java -согласование ... Может быть, я должен написать более подробный ответ, который ясно показывает, как этот набросок применим к Java ...
Андрей Тюкин
3
Это одно из самых простых и ясных объяснений Ковариации и Контравариантности, которые я когда-либо нашел!
cs4r
@ Андрей Тюкин Привет, я тоже хочу использовать это изображение. Как я могу связаться с вами?
Используйте подстановочный знак extends, когда вы получаете значения только из структуры.
Используйте супер подстановочный знак, когда вы только помещаете значения в структуру.
И не используйте подстановочный знак, когда вы оба получаете и ставите.
Пример на Java:
classSuper{Object testCoVariance(){returnnull;}//Covariance of return types in the subtype.void testContraVariance(Object parameter){}// Contravariance of method arguments in the subtype.}classSubextendsSuper{@OverrideString testCoVariance(){returnnull;}//compiles successfully i.e. return type is don't care(String is subtype of Object) @Overridevoid testContraVariance(String parameter){}//doesn't support even though String is subtype of Object}
Принцип подстановки Лискова: если S является подтипом T, то объекты типа T могут быть заменены объектами типа S.
В системе типов языка программирования, правило ввода
ковариантный, если он сохраняет порядок типов (≤), который упорядочивает типы от более специфических к более общим;
контравариантно, если оно отменяет этот порядок;
инвариантный или неизменный, если ни один из них не применим.
Типы данных (источники) только для чтения могут быть ковариантными ;
Только для записи типы данных (приемники) могут быть контравариантными .
Изменяемые типы данных, которые действуют как источники и приемники, должны быть инвариантными .
Чтобы проиллюстрировать это общее явление, рассмотрим тип массива. Для типа Animal мы можем сделать тип Animal []
ковариант : кот [] - это животное [];
противоречивый : животное [] - это кошка [];
инвариант : животное [] не является котом [], а кот [] не является животным [].
Примеры Java:
Object name=newString("prem");//worksList<Number> numbers =newArrayList<Integer>();//gets compile time errorInteger[] 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=newArrayList<>();
list.add("prem");List<Object> listObject=list;//Type mismatch: cannot convert from List<String> to List<Object> at Compiletime
ограниченный (т.е. направленный куда-то) подстановочный знак : есть 3 различных вида подстановочных знаков:
In-дисперсия / Non-дисперсия: ?или ? extends Object- Неограниченный подстановочный знак. Это обозначает семью всех типов. Используйте, когда вы оба получите и положите.
Co-дисперсия: ? extends T(семейство всех типов, являющихся подтипами T) - подстановочный знак с верхней границей . Tсамый верхний класс в иерархии наследования. Используйте extendsподстановочный знак, когда вы только получаете значения из структуры.
Противопоставление: ? super T(семейство всех типов, являющихся супертипами T) - подстановочный знак с нижней границей . Tсамый нижний класс в иерархии наследования. Используйте superподстановочный знак, когда вы только помещаете значения в структуру.
Примечание: подстановочный знак ?означает ноль или один раз , представляет неизвестный тип. Подстановочный знак может использоваться как тип параметра, никогда не использоваться в качестве аргумента типа для вызова универсального метода, создания экземпляра универсального класса (т. Е. Когда используется подстановочный знак, ссылка на который не используется в других частях программы, как мы используем T)
classShape{void draw(){}}classCircleextendsShape{void draw(){}}classSquareextendsShape{void draw(){}}classRectangleextendsShape{void draw(){}}publicclassTest{/*
* Example for an upper bound wildcard (Get values i.e Producer `extends`)
*
* */publicvoid testCoVariance(List<?extendsShape> list){
list.add(newShape());// Error: is not applicable for the arguments (Shape) i.e. inheritance is not supporting
list.add(newCircle());// Error: is not applicable for the arguments (Circle) i.e. inheritance is not supporting
list.add(newSquare());// Error: is not applicable for the arguments (Square) i.e. inheritance is not supporting
list.add(newRectangle());// Error: is not applicable for the arguments (Rectangle) i.e. inheritance is not supportingShape 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`)
* */publicvoid testContraVariance(List<?superShape> list){
list.add(newShape());//compiles i.e. inheritance is supporting
list.add(newCircle());//compiles i.e. inheritance is supporting
list.add(newSquare());//compiles i.e. inheritance is supporting
list.add(newRectangle());//compiles i.e. inheritance is supportingShape shape= list.get(0);// Error: Type mismatch, so list acts only as consumerObject 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.
*/}}
Эй, я просто хотел узнать, что вы имели в виду в последнем предложении: «Если вы считаете, что моя аналогия неверна, пожалуйста, обновите». Вы имеете в виду, если это этически неправильно (это субъективно) или если это неправильно в контексте программирования (что объективно: нет, это не так)? Я хотел бы заменить его более нейтральным примером, который является универсально приемлемым, независимо от культурных норм и этических убеждений; Если это нормально с тобой.
Нейрон
наконец я мог получить это. Хорошее объяснение.
Олег Куц
2
@Premraj, In-variance/Non-variance: ? or ? extends Object - Unbounded Wildcard. It stands for the family of all types. Use when you both get and put.я не могу добавить элемент в список <?> Или список <? расширяет объект>, поэтому я не понимаю, почему это может быть Use when you both get and put.
LiuWenbin_NO.
1
@LiuWenbin_NO. - Эта часть ответа вводит в заблуждение. ?- «неограниченный подстановочный знак» - соответствует полной противоположности инвариантности. Пожалуйста, обратитесь к следующей документации: docs.oracle.com/javase/tutorial/java/generics/…, которая гласит: В случае, когда коду требуется доступ к переменной как к переменным «in», так и к «out», выполните не используйте подстановочный знак. (Они используют «in» и «out» как синонимы «get» и «put»). За исключением того, что nullвы не можете добавить в коллекцию с параметром ?.
mouselabs
29
publicclassTest{publicclass A {}publicclass B extends A {}publicclass C extends B {}publicvoid 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);}publicvoid 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}}
Так что «? Расширяет B» следует интерпретировать как «? Расширяет B». Это то, что B расширяет, так что будет включать в себя все суперклассы B вплоть до Object, за исключением самого B. Спасибо за код!
Саураб Патил
3
@SaurabhPatil Нет, ? extends Bозначает B и все, что расширяет B.
спрашивает
24
Как я объясняю в своем ответе на другой вопрос, PECS - это мнемоническое устройство, созданное Джошем Блохом, чтобы помочь вспомнить P roducer extends, C onsumer super.
Это означает, что когда параметризованный тип, передаваемый методу, будет генерировать экземпляры T(они будут каким-то образом извлечены из него), его ? extends Tследует использовать, поскольку любой экземпляр подкласса Tтакже является a 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>) ---> здесь Comparator использует super, поэтому это означает, что он является потребителем в контексте PECS. Когда вы смотрите на реализацию, например: public int Compare (Person a, Person b) {return a.age <b.age? -1: a.age == b.age? 0: 1; } Я чувствую, что человек ничего не потребляет, но рождает возраст. Это меня смущает. Есть ли в моих рассуждениях недостаток или PECS действует только для коллекций?
Фатих Арслан
24
Короче говоря, три простых правила, чтобы запомнить PECS:
Используйте <? extends T>подстановочный знак, если вам нужно получить объект типа Tиз коллекции.
Используйте <? super T>подстановочный знак, если вам нужно поместить объекты типа Tв коллекцию.
Если вам нужно удовлетворить обе вещи, не используйте подстановочные знаки. Так просто.
classCreature{}// XclassAnimalextendsCreature{}// YclassFishextendsAnimal{}// ZclassSharkextendsFish{}// AclassHammerSkarkextendsShark{}// BclassDeadHammerSharkextendsHammerSkark{}// C
Давайте уточним PE - Производитель расширяет:
List<?extendsShark> sharks =newArrayList<>();
Почему вы не можете добавить объекты, которые расширяют "Акула" в этом списке? подобно:
sharks.add(newHammerShark());//will result in compilation error
Поскольку у вас есть список, который может быть типа A, B или C во время выполнения , вы не можете добавить в него любой объект типа A, B или C, потому что вы можете получить комбинацию, которая не разрешена в Java. На практике компилятор действительно может видеть во время компиляции, что вы добавляете B:
sharks.add(newHammerShark());
... но он не может определить, будет ли во время выполнения ваш B подтипом или супертипом типа списка. Во время выполнения тип списка может быть любым из типов A, B, C. Таким образом, вы не можете добавить HammerSkark (супертип) в список, например, DeadHammerShark.
* Вы скажете: «Хорошо, но почему я не могу добавить в него HammerSkark, так как это самый маленький тип?». Ответ: это самое маленькое, что вы знаете. Но HammerSkark может быть расширен кем-то другим, и вы в конечном итоге в том же сценарии.
Вы можете добавить вышеупомянутые типы объектов, потому что все, что находится ниже акулы (A, B, C), всегда будет подтипом чего-либо, что находится выше акулы (X, Y, Z). Легко понять.
Вы не можете добавлять типы выше Shark, потому что во время выполнения тип добавленного объекта может быть выше в иерархии, чем объявленный тип списка (X, Y, Z). Это не разрешено
Но почему вы не можете прочитать из этого списка? (Я имею в виду, что вы можете извлечь из него элемент, но вы не можете назначить его чему-либо, кроме Object o):
Object o;
o = sharks.get(2);// only assignment that worksAnimal s;
s = sharks.get(2);//doen't work
Во время выполнения тип списка может быть любого типа выше A: X, Y, Z, ... Компилятор может скомпилировать ваш оператор присваивания (который кажется правильным), но во время выполнения тип s (Animal) может быть ниже в иерархия, чем объявленный тип списка (который может быть Существо или выше). Это не разрешено
Подводить итоги
Мы используем <? 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);// DestinationList<Integer> intList2 =newArrayList<>();List<Double> doublesList2 =newArrayList<>();List<Number> numList2 =newArrayList<>();// Works
copyElements1(intList,intList2);// from int to int
copyElements1(doubleList,doublesList2);// from double to doublestatic<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)privatestatic<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, ориентированное на коллекцию, должно иметь метод с именем «receive ()» вместо «add ()» - в конце концов,элемент, но сам список получает элемент.
Я думаю, что более интуитивно, естественно и непротиворечиво думать о вещах с точки зрения кода, который взаимодействует с коллекцией - код «читает из» или «записывает» в коллекцию? После этого любой код, записывающий в коллекцию, будет «производителем», а любой код, читающий из коллекции, будет «потребителем».
Я столкнулся с тем же психическим столкновением и буду склонен согласиться разве что PECS не указует именование кода и границу типа сами являются установленной на декларациях коллекции. Более того, что касается именования, у вас часто есть имена для создания / потребления Коллекций, таких как srcи dst. Таким образом, вы имеете дело как с кодом, так и с контейнерами одновременно, и я в конечном итоге задумался над этим следующим образом: «потребляющий код» потребляет из производящего контейнера, а «производящий код» производит для потребляющего контейнера.
mouselabs
4
«Правило» 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.
publicclass A {}//B is Apublicclass B extends A {}//C is Apublicclass C extends A {}
Обобщения позволяют безопасно и динамически работать с типами.
//ListAList<A> listA =newArrayList<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);
//ListBList<B> listB =newArrayList<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 и всеми суперклассами, поэтому его можно безопасно добавлять.
<? extends A>ака верхняя граница ака ковариантность ака производителей гарантирует, что он работает по A и всем подклассам, поэтому его безопасно получать и разыгрывать
List<?extends A> listExtendsA;
listExtendsA = listA;
listExtendsA = listB;//get
A a0 = listExtendsA.get(0);
super
часть, но дает представление о другой.Ответы:
tl; dr: «PECS» с точки зрения коллекции. Если вы берете только предметы из общей коллекции, это производитель, и вы должны использовать его
extends
; если вы только набиваете предметы, это потребитель, и вы должны его использоватьsuper
. Если вы делаете оба с одной коллекцией, вы не должны использовать либоextends
илиsuper
.Предположим, у вас есть метод, который принимает в качестве параметра набор вещей, но вы хотите, чтобы он был более гибким, чем просто принятие a
Collection<Thing>
.Случай 1: Вы хотите просмотреть коллекцию и сделать что-то с каждым предметом.
Тогда список является производителем , поэтому вы должны использовать
Collection<? extends Thing>
.Причина заключается в том, что a
Collection<? extends Thing>
может содержать любой подтипThing
, и поэтому каждый элемент будет вести себя как a,Thing
когда вы выполняете свою операцию. (На самом деле вы ничего не можете добавить к aCollection<? extends Thing>
, потому что вы не можете знать во время выполнения, какой конкретный подтипThing
коллекции содержит.)Случай 2: Вы хотите добавить вещи в коллекцию.
Тогда список является потребителем , поэтому вы должны использовать
Collection<? super Thing>
.Причиной здесь является то
Collection<? extends Thing>
, что в отличие от этого ,Collection<? super Thing>
всегда может хранитьсяThing
независимо от того, что является фактическим параметризованным типом. Здесь вас не волнует, что уже есть в списке, если это позволит добавить aThing
; это то, что? super Thing
гарантирует.источник
doSomethingWithList(List list)
, вы потребляя список и поэтому нужно будешь ковариация / продолжается (или инвариантное List). С другой стороны , если ваш методList doSomethingProvidingList
, то вы производить этот список и будет нуждаться в контравариации / супер (или инвариантное List).const
параметры метода в C ++ для обозначения того, что метод не изменяет аргументы?Принципы, стоящие за этим в информатике называется
? extends MyClass
,? super MyClass
иMyClass
Картинка ниже должна объяснить концепцию. Фото предоставлено Андреем Тюкиным
источник
PECS (Производитель
extends
и Потребительsuper
)мнемоника → принцип «получи и положи».
Этот принцип гласит, что:
Пример на Java:
Принцип подстановки Лискова: если S является подтипом T, то объекты типа T могут быть заменены объектами типа S.
В системе типов языка программирования, правило ввода
Ковариация и контравариантность
Чтобы проиллюстрировать это общее явление, рассмотрим тип массива. Для типа Animal мы можем сделать тип Animal []
Примеры Java:
больше примеров
ограниченный (т.е. направленный куда-то) подстановочный знак : есть 3 различных вида подстановочных знаков:
?
или? extends Object
- Неограниченный подстановочный знак. Это обозначает семью всех типов. Используйте, когда вы оба получите и положите.? extends T
(семейство всех типов, являющихся подтипамиT
) - подстановочный знак с верхней границей .T
самый верхний класс в иерархии наследования. Используйтеextends
подстановочный знак, когда вы только получаете значения из структуры.? super T
(семейство всех типов, являющихся супертипамиT
) - подстановочный знак с нижней границей .T
самый нижний класс в иерархии наследования. Используйтеsuper
подстановочный знак, когда вы только помещаете значения в структуру.Примечание: подстановочный знак
?
означает ноль или один раз , представляет неизвестный тип. Подстановочный знак может использоваться как тип параметра, никогда не использоваться в качестве аргумента типа для вызова универсального метода, создания экземпляра универсального класса (т. Е. Когда используется подстановочный знак, ссылка на который не используется в других частях программы, как мы используемT
)дженерики и примеры
источник
In-variance/Non-variance: ? or ? extends Object - Unbounded Wildcard. It stands for the family of all types. Use when you both get and put.
я не могу добавить элемент в список <?> Или список <? расширяет объект>, поэтому я не понимаю, почему это может бытьUse when you both get and put
.?
- «неограниченный подстановочный знак» - соответствует полной противоположности инвариантности. Пожалуйста, обратитесь к следующей документации: docs.oracle.com/javase/tutorial/java/generics/…, которая гласит: В случае, когда коду требуется доступ к переменной как к переменным «in», так и к «out», выполните не используйте подстановочный знак. (Они используют «in» и «out» как синонимы «get» и «put»). За исключением того, чтоnull
вы не можете добавить в коллекцию с параметром?
.источник
? extends B
означает B и все, что расширяет B.Как я объясняю в своем ответе на другой вопрос, PECS - это мнемоническое устройство, созданное Джошем Блохом, чтобы помочь вспомнить P roducer
extends
, C onsumersuper
.Обратите внимание , что , как правило , вы должны быть только с помощью
? extends T
и? super T
для параметров некоторого метода. Методы должны просто использоватьсяT
в качестве параметра типа общего возвращаемого типа.источник
Короче говоря, три простых правила, чтобы запомнить PECS:
<? extends T>
подстановочный знак, если вам нужно получить объект типаT
из коллекции.<? super T>
подстановочный знак, если вам нужно поместить объекты типаT
в коллекцию.источник
давайте предположим эту иерархию:
Давайте уточним PE - Производитель расширяет:
Почему вы не можете добавить объекты, которые расширяют "Акула" в этом списке? подобно:
Поскольку у вас есть список, который может быть типа A, B или C во время выполнения , вы не можете добавить в него любой объект типа A, B или C, потому что вы можете получить комбинацию, которая не разрешена в Java.
На практике компилятор действительно может видеть во время компиляции, что вы добавляете B:
... но он не может определить, будет ли во время выполнения ваш B подтипом или супертипом типа списка. Во время выполнения тип списка может быть любым из типов A, B, C. Таким образом, вы не можете добавить HammerSkark (супертип) в список, например, DeadHammerShark.
* Вы скажете: «Хорошо, но почему я не могу добавить в него HammerSkark, так как это самый маленький тип?». Ответ: это самое маленькое, что вы знаете. Но HammerSkark может быть расширен кем-то другим, и вы в конечном итоге в том же сценарии.
Давайте поясним CS - Consumer Super:
В той же иерархии мы можем попробовать это:
Что и почему вы можете добавить в этот список?
Вы можете добавить вышеупомянутые типы объектов, потому что все, что находится ниже акулы (A, B, C), всегда будет подтипом чего-либо, что находится выше акулы (X, Y, Z). Легко понять.
Вы не можете добавлять типы выше Shark, потому что во время выполнения тип добавленного объекта может быть выше в иерархии, чем объявленный тип списка (X, Y, Z). Это не разрешено
Но почему вы не можете прочитать из этого списка? (Я имею в виду, что вы можете извлечь из него элемент, но вы не можете назначить его чему-либо, кроме Object o):
Во время выполнения тип списка может быть любого типа выше A: X, Y, Z, ... Компилятор может скомпилировать ваш оператор присваивания (который кажется правильным), но во время выполнения тип s (Animal) может быть ниже в иерархия, чем объявленный тип списка (который может быть Существо или выше). Это не разрешено
Подводить итоги
Мы используем
<? super T>
для добавления объектов типов, равных или нижеT
кList
. Мы не можем читать из этого.Мы используем
<? extends T>
для чтения объектов типов, равных или нижеT
из списка. Мы не можем добавить элемент к нему.источник
(добавив ответ, потому что примеров с подстановочными знаками Generics недостаточно)
источник
Это самый ясный и простой способ для меня думать о расширяется против супер:
extends
для чтенияsuper
для написанияЯ считаю, что «PECS» - это неочевидный способ думать о том, кто является «производителем», а кто «потребителем». «PECS» определяется с точки зрения самого сбора данных - коллекция «потребляет», если в нее записываются объекты (она потребляет объекты из вызывающего кода), и «выдает», если объекты читаются из нее (это создает объекты для некоторого вызывающего кода). Это противоречит тому, как все остальное называется. Стандартные API Java названы с точки зрения вызывающего кода, а не самой коллекции. Например, представление java.util.List, ориентированное на коллекцию, должно иметь метод с именем «receive ()» вместо «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>
.Когда я впервые столкнулся с этим и сломал его в своей голове, механика имела смысл, но сам код продолжал казаться мне запутанным - я продолжал думать, что «кажется, что границы не должны быть так перевернуты» - хотя я По вышесказанному было ясно - что речь идет просто о гарантии соблюдения стандартных правил ведения.
Что помогло мне, так это обычное задание в качестве аналогии.
Рассмотрим следующий (не готовый к производству) игрушечный код:
Иллюстрируя это с точки зрения аналогии назначения, для
consumer
в?
подстановки (неизвестный тип) ссылка - «левая сторона» на уступки - и<? super T>
гарантирует , что все?
это,T
«IS-A»?
- чтоT
может быть возложены на него, потому что?
это супер тип (или не более того же типа), какT
.Для
producer
концерна это то же самое , это просто перевернутая:producer
«s?
подстановочные (неизвестный тип) является референтом - в„правую“выполнения задания - и<? extends T>
гарантирует , что все?
это,?
„IS-A“T
- что он может быть назначен на аT
, потому что?
это подтип (или, по крайней мере, тот же тип), что иT
.источник
Запомните это:
источник
Используя пример из реальной жизни (с некоторыми упрощениями):
<? super FreightCarSize>
<? extends DepotSize>
источник
Ковариантность : принять подтипы.
Контравариантность : принять подтипы .
Ковариантные типы доступны только для чтения, а контравариантные - только для записи.
источник
Давайте посмотрим на пример
Обобщения позволяют безопасно и динамически работать с типами.
проблема
Поскольку коллекция Java является ссылочным типом, в результате мы имеем следующие проблемы:
Проблема № 1
* У универсала Swift такой проблемы нет, потому что Collection - это
Value type
[About], поэтому создается новая коллекция.Проблема № 2
Решение - универсальные шаблоны
Подстановочный знак является признаком ссылочного типа, и его нельзя создать напрямую
Решение # 1, также
<? super A>
известное как нижняя граница, также противоречащее потребителям, гарантирует, что оно управляется A и всеми суперклассами, поэтому его можно безопасно добавлять.Решение № 2
<? extends A>
ака верхняя граница ака ковариантность ака производителей гарантирует, что он работает по A и всем подклассам, поэтому его безопасно получать и разыгрыватьисточник