Что такое УЭК (производитель расширяет потребительский супер)?
я наткнулся на УИК (сокращение от производитель extends
и потребителем super
) при чтении на дженерики.
может кто-нибудь объяснить мне, как использовать УИК для разрешения путаницы между extends
и super
?
11 ответов:
tl; dr: "УИК" - это с точки зрения коллекции. Если ты только вытягивая элементы из общей коллекции, это производитель, и вы должны использовать
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
гарантии.
принципы, лежащие в основе этого в информатике, названы в честь
- ковариация - ? расширяет класса MyClass
- контравариантность - ? супер MyClass и
- инвариантность / не-дисперсия-MyClass
изображение ниже должно объяснить концепцию.
Фото предоставлено : Андрей Тюкин
PECS (сокращение от"производитель
extends
и потребителемsuper
") можно объяснить : получить и поставить принциппринцип Get и Put (из Java Generics и коллекций)
в нем говорится,
- использовать расширяет подстановочный знак когда вы только get значения из структуры
- использовать супер подстановочный знак когда вы только поставить значения в a структура
- и не используйте подстановочный знак когда вы как получить, так и поставить.
давайте разберемся на примере:
1. Для расширенного подстановочного знака (получить значения, т. е. 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
)вот метод, который принимает набор чисел и
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
специальный символ, за исключением значения типа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 }
Лисков принципа замещения:массивы являются ковариантными(небезопасными), но дженерики не являются т. е. инвариантными (безопасными). т. е. принцип подстановки не работает с параметризованными типами, что означает, что писать незаконно.
Ковариант просто означает, если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-variance:
? 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
,Cпотребителямsuper
.это означает, что при передаче параметризованного типа в метод будет производства экземпляров
T
(они будут извлечены из него в некотором роде),? extends T
следует использовать, так как любой экземпляр подклассаT
также aT
.когда параметризованный тип передается методу будет потреблять экземпляров
T
(они будут переданы ему что-то сделать),? super T
следует использовать, потому что экземплярT
может быть юридически передан любому методу, который принимает некоторый супертипT
. АComparator<Number>
наCollection<Integer>
, например.? extends T
не будет работать, потому чтоComparator<Integer>
не может работать наCollection<Number>
.обратите внимание, что в целом вы должны использовать только
? extends T
и? super T
для параметров некоторых методов. Методы должны просто использоватьT
в качестве параметра типа для универсального возвращаемого типа.
в двух словах легко запомнить УИК
- использовать
<? 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<>();
что и почему ты можете добавить в этот список?
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 из списка. мы не можем добавить элемент к нему.
подстановочные знаки можно использовать тремя способами:
- 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.