Yanıtlar:
tl; dr: "PECS" koleksiyonun bakış açısından. Eğer varsa sadece genel bir koleksiyondan öğe çekerek, bu bir üreticidir ve kullanmak gerekir extends
; Eğer varsa sadece öğeleri doldurma, bir tüketici olduğunu ve kullanmalıdır super
. Eğer ikisi de aynı koleksiyonu ile yaparsanız, ya kullanmamalısınız extends
ya super
.
Parametre olarak bir şeyler koleksiyonu alan bir yönteminiz olduğunu, ancak yalnızca a'yı kabul etmekten daha esnek olmasını istediğinizi varsayalım Collection<Thing>
.
Durum 1: Koleksiyondan geçmek ve her bir öğe ile bir şeyler yapmak istiyorsunuz.
O zaman liste bir yapımcıdır , bu yüzden bir kullanmalısınız Collection<? extends Thing>
.
Bunun nedeni, Collection<? extends Thing>
a'nın herhangi bir alt türüne sahip olabilmesidir Thing
ve bu nedenle her bir öğe, Thing
işleminizi gerçekleştirirken bir gibi davranacaktır . (Aslında bir şey ekleyemezsiniz Collection<? extends Thing>
çalışma zamanında bilemez çünkü, belirli bir alt tipi Thing
koleksiyonunda tutar.)
Durum 2: Koleksiyona bir şeyler eklemek istiyorsunuz.
O zaman liste bir tüketicidir , bu yüzden bir Collection<? super Thing>
.
Burada akıl aksine olmasıdır Collection<? extends Thing>
, Collection<? super Thing>
her zaman tutabilir Thing
olursa olsun fiili parametreli tür olduğunu. Burada, a'nın Thing
eklenmesine izin verdiği sürece listede nelerin bulunduğunu umursamazsınız ; bunu ? super Thing
garanti eder.
doSomethingWithList(List list)
varsa , listeyi tüketiyorsunuzdur ve bu nedenle kovaryans / genişletmeler (veya değişmez bir Liste) gerekir. Öte yandan yönteminiz ise List doSomethingProvidingList
, o zaman Liste üretiyorsunuzdur ve karşıtlık / süper (ya da değişmez bir Liste) gerekecektir.
const
yöntemin argümanları değiştirmediğini belirtmek için C ++ 'da yöntem parametreleri olarak başvuruları kullanmaya benzer şekilde okunabilirliği geliştirmenin bir yolu var mı?
Bilgisayar biliminde bunun arkasında yatan ilkelere
? extends MyClass
,? super MyClass
veMyClass
Aşağıdaki resim kavramı açıklamalıdır. Resim nezaket: Andrey Tyukin
PECS (Üretici extends
ve Tüketici super
)
anımsatıcı → Al ve koy ilkesi.
Bu ilke şunları ifade eder:
Java örneği:
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 ikame prensibi: S, T'nin bir alt tipiyse, T tipi nesnelerin yerine S tipi nesneler kullanılabilir.
Bir programlama dilinin tip sistemi içinde bir yazma kuralı
Bu genel durumu göstermek için dizi türünü göz önünde bulundurun. Hayvan türü için Hayvan türü yapabiliriz []
Java Örnekleri:
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
sınırlı (yani bir yere doğru ilerleyen) joker karakter : 3 farklı joker karakter çeşidi vardır:
?
veya ? extends Object
- Sınırsız Joker Karakter. Her türlü ailenin kısaltmasıdır. Hem alıp koyduğunuzda kullanın.? extends T
(alt tipleri olan her türden aile T
) - üst sınırı olan bir joker karakter . T
olan üst kalıtım hiyerarşisinde -En sınıfı. Bir kullan extends
yalnızca zaman joker alın bir yapının dışına değerlerini.? super T
(süper tipleri olan her türden aile T
) - alt sınırı olan bir joker karakter . T
olduğu alt kalıtım hiyerarşisinde -En sınıfı. Bir kullan super
yalnızca zaman joker koyun bir yapıya değerleri.Not: joker karakter sıfır veya bir kez?
anlamına gelir , bilinmeyen bir türü temsil eder. Joker genel bir yöntem çağırma, genel bir sınıfı örneği oluşturulması için bir tip değişken olarak kullanılan hiç bir parametrenin türü olarak kullanılabilir. (Yani kullandığımız benzer referans programında başka bir yerde kullanılan değil ikinci Joker )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.
<?> Veya <Liste'ye öğe ekleyemiyorum? Object> öğesini genişletir, bu yüzden neden olabileceğini anlamıyorum Use when you both get and put
.
?
- "sınırsız joker karakter" - değişmezliğin tam tersine karşılık gelir. Lütfen aşağıdaki belgelere bakın: docs.oracle.com/javase/tutorial/java/generics/… belirten: Kodun değişkene hem "giriş" hem de "çıkış" değişkeni olarak erişmesi gerektiğinde, joker karakter kullanmayın. ("Get" ve "put" ile eşanlamlı olarak "in" ve "out" ifadelerini kullanıyorlar). Dışında null
bir Koleksiyonu ile parametrelendirmek size ekleyemezsiniz ?
.
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 anlamına gelir ve B'yi genişleten herhangi bir şey
Ben açıklamak üzere cevabım başka soruya, PECS yardımına Josh Bloch tarafından oluşturulan bir anımsatıcı cihaz hatırlamak olduğunu P roducer extends
, C onsumer super
.
Bu, bir yönteme geçirilen parametreli bir türün , bir alt sınıfının herhangi bir örneği de a olduğu için (bir şekilde ondan alınacak) örnekleri üreteceği anlamına gelir .
T
? extends T
T
T
Bir yönteme geçirilen parametreli bir türün (bir şey yapmak için kendisine aktarılacak) örneklerini tüketeceği zaman kullanılmalıdır, çünkü bir örneği, bazı üst türlerini kabul eden herhangi bir yönteme yasal olarak geçirilebilir . Bir bir ile kullanılabilir , örneğin. işe yaramaz, çünkü a .
T
? super T
T
T
Comparator<Number>
Collection<Integer>
? extends T
Comparator<Integer>
Collection<Number>
Genel olarak yalnızca ? extends T
ve ? super T
bazı yöntemlerin parametrelerini kullanmanız gerektiğini unutmayın . Yöntemler yalnızca T
genel bir dönüş türünde type parametresi olarak kullanılmalıdır .
Özetle, PECS'yi hatırlamak için üç kolay kural:
<? extends T>
Tür nesnesini almanız gerekiyorsa joker karakteri kullanınT
Bir koleksiyondan .<? super T>
Türdeki nesneleri koymanız gerekiyorsa joker karakteri kullanınT
Bir koleksiyona .bu hiyerarşiyi varsayalım:
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'yi açıklayalım - Üretici Kapsamı:
List<? extends Shark> sharks = new ArrayList<>();
Neden bu listeye "Köpekbalığı" nı genişleten nesneler ekleyemiyorsunuz? sevmek:
sharks.add(new HammerShark());//will result in compilation error
Çalışma zamanında A, B veya C türünde olabilen bir listeniz olduğundan, java'da izin verilmeyen bir kombinasyon oluşturabileceğiniz için A, B veya C türünde herhangi bir nesne ekleyemezsiniz.
Pratikte, derleyici gerçekten derleme sırasında B eklediğinizi görebilir:
sharks.add(new HammerShark());
... ancak çalışma zamanında B'nizin liste türünün bir alt türü veya süper türü olup olmadığını anlamanın bir yolu yoktur. Çalışma zamanında liste türü A, B, C türlerinden herhangi biri olabilir. Böylece örneğin DeadHammerShark listesine HammerSkark (süper tip) eklemeniz mümkün değildir.
* "Tamam, ama neden en küçük tip olduğu için HammerSkark'ı ekleyemiyorum?" Cevap: Bildiğiniz en küçük şey . Ancak HammerSkark başka biri tarafından da genişletilebilir ve aynı senaryoya girersiniz.
CS - Tüketici Süper:
Aynı hiyerarşide bunu deneyebiliriz:
List<? super Shark> sharks = new ArrayList<>();
Ne ve neden olabilir bu listeye eklemek?
sharks.add(new Shark());
sharks.add(new DeadHammerShark());
sharks.add(new HammerSkark());
Yukarıdaki nesne türlerini ekleyebilirsiniz, çünkü köpekbalığının altındaki her şey (A, B, C) her zaman köpekbalığının üzerindeki her şeyin alt tipleri olacaktır (X, Y, Z). Anlaması kolay.
Sen olamaz , çünkü Shark yukarıdaki türleri eklemek zamanında eklenen nesnenin türü listesinde (X, Y, Z) belirtilen türüne göre hiyerarşide daha yüksek olabilir. Buna izin verilmiyor.
Ama neden bu listeden okuyamıyorsunuz? (Demek istediğim, ondan bir öğe alabilirsin, ama bunu Object o dışında bir şeye atayamazsın):
Object o;
o = sharks.get(2);// only assignment that works
Animal s;
s = sharks.get(2);//doen't work
Çalışma zamanında, liste türü A'nın üstündeki herhangi bir tür olabilir: X, Y, Z, ... Derleyici atama ifadenizi (doğru görünüyor) derleyebilir, ancak çalışma zamanında s (Hayvan) türü daha düşük olabilir hiyerarşi listenin beyan edilen türüne göre (Yaratık veya üstü olabilir). Buna izin verilmiyor.
Sonuç olarak
Biz kullanmak <? super T>
türdeki nesnelerin eşit veya altında eklemek T
için List
. Ondan okuyamayız. Listeden eşit veya aşağı tipte nesneleri okumak için
kullanırız . <? extends T>
T
Ona eleman ekleyemeyiz.
(bir cevap eklemek, çünkü Generics joker karakterleriyle asla yeterli örnek vermemek)
// 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);
}
}
Bu benim için süper genişletir düşünüyorum en açık, en basit yolu:
extends
içindir okuma
super
içindir yazı
"PECS" i kimin "üretici" ve kimin "tüketici" olduğu hakkında düşünmenin açık olmayan bir yolu olarak görüyorum. "PECS" veri toplamanın kendisi açısından tanımlanır nesneler yazılıyor eğer toplama "tükettiğini" - için (o çağıran koddan nesneleri tüketen) o, ve nesneler okunduğunu eğer o "üretir" dan (bunu o bazı arama kodlarına nesne üretiyor). Bu, her şeyin nasıl adlandırıldığının tersidir. Standart Java API'leri, koleksiyonun kendisi değil, arama kodunun perspektifinden adlandırılır. Örneğin, java.util.List öğesinin koleksiyon merkezli bir görünümünde "add ()" yerine "take ()" adlı bir yöntem bulunmalıdır - sonuçta,ancak listenin kendisi öğeyi alır .
Ben koleksiyon ile etkileşen kod perspektifinden şeyler düşünmek daha sezgisel, doğal ve tutarlı - kod "okuma" veya "koleksiyon" yazma "mı? Bunu takiben , koleksiyona yazılan herhangi bir kod "üretici" ve koleksiyondan okunan herhangi bir kod "tüketici" olacaktır.
src
ve gibi Koleksiyonlar üretmek / tüketmek için genellikle adlarınız vardır dst
. Yani hem kod hem de kapsayıcılarla aynı anda ilgileniyorsunuz ve bu satırlar üzerinde düşünmeye başladım - "kod tüketmek", üreten bir kaptan tüketiyor ve "kod üretmek", tüketen bir kap için üretiyor.
PECS "kuralı" sadece aşağıdakilerin yasal olmasını sağlar:
?
, yasal olarak başvurabilir T
?
, bu yasal olarak alınabilir tarafından sevk T
Çizgileri boyunca tipik eşleştirme List<? extends T> producer, List<? super T> consumer
, derleyicinin standart "IS-A" kalıtım ilişkisi kurallarını uygulayabilmesini sağlamaktır. Bunu yasal olarak yapabilseydik, daha kolay söyleyebiliriz <T extends ?>, <? extends T>
(ya da Scala'da yukarıda gördüğünüz gibi [-T], [+T]
daha iyi olabilir) <? super T>, <? extends T>
.
Bunu ilk karşılaştığımda ve kafamda bozduğumda, mekanikler mantıklıydı ama kodun kendisi bana kafa karıştırıcı görünmeye devam etti - "sınırların bu şekilde ters çevrilmesi gerekmiyor gibi görünüyor" diye düşündüm - yukarıdakilere açıktı - sadece standart referans kurallarına uyumu garanti etmekle ilgili.
Bana yardımcı olan şey, sıradan ödevi bir benzetme olarak kullanarak bakmaktı.
Aşağıdaki (üretime hazır değil) oyuncak kodunu göz önünde bulundurun:
// 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);
}
İçin, atama benzetme bakımından Bunun açıklanması atamanın "sol taraftaki" - - ve referans joker (bilinmeyen tip) olduğu her şeyin olmasını sağlar olduğunu "-A IS" - buna atanabilir, çünkü olarak bir süper tiptir (veya en fazla aynı tiptir) .consumer
?
<? super T>
?
T
?
T
?
T
İçin producer
endişe sadece ters oluyor aynıdır: producer
'ın ?
joker (bilinmeyen tip)' dir referent - atama 'sağ tarafını' - ve <? extends T>
her ne olmasını sağlar ?
olduğunu ?
'-A IS' T
- yani o atanabilir a kadarT
, çünkü ?
bir alt türdür (veya en azından aynı tür) T
.
Gerçek hayat örneği kullanma (bazı basitleştirmelerle):
<? super FreightCarSize>
<? extends DepotSize>
Kovaryans : alt tipleri kabul et
Contravariance : üst tipleri kabul et
Kovaryant tipler salt okunurken kontravaryant tipler salt okunurdur.
Örnek bir göz atalım
public class A { }
//B is A
public class B extends A { }
//C is A
public class C extends A { }
Generics, Türlerle dinamik olarak güvenli bir şekilde çalışmanıza olanak tanır
//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'nın Koleksiyonu sonuç olarak bir referans türü olduğundan, sonraki sorunlarımız var:
Sorun # 1
//not compiled
//danger of **adding** non-B objects using listA reference
listA = listB;
* Swift'in jenerikinde böyle bir sorun yok çünkü Koleksiyon Value type
[Hakkında] olduğundan yeni bir koleksiyon oluşturuldu
Sorun # 2
//not compiled
//danger of **getting** non-B objects using listB reference
listB = listA;
Joker karakter bir başvuru türü özelliğidir ve doğrudan somutlaştırılamaz
Çözüm # 1
<? super A>
aka alt sınır aka karşıtlık aka tüketiciler, A ve tüm üst sınıflar tarafından çalışacağını garanti eder, bu yüzden eklemek güvenlidir
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);
Çözüm # 2
<? extends A>
aka üst sınır aka kovaryans aka üreticiler, A ve tüm alt sınıflar tarafından işletildiğini garanti eder, bu yüzden almak ve dökmek güvenlidir
List<? extends A> listExtendsA;
listExtendsA = listA;
listExtendsA = listB;
//get
A a0 = listExtendsA.get(0);
super
ancak başka bir fikir veren çok iyi bir açıklama .