<Arasındaki fark nedir? süper E> ve <? E>?


147

Arasındaki fark nedir <? super E>ve <? extends E>?

Örneğin, sınıfa baktığınızda java.util.concurrent.LinkedBlockingQueue, kurucu için aşağıdaki imza vardır:

public LinkedBlockingQueue(Collection<? extends E> c)

ve biri için yöntem:

public int drainTo(Collection<? super E> c)

Yanıtlar:


182

Birincisi, “E'nin atası olan bir tür” olduğunu söylüyor; ikincisi bunun "E'nin bir alt sınıfı olan bir tür" olduğunu söylüyor. (Her iki durumda da E'nin kendisi iyidir.)

Yapıcı kullandığı Yani ? extends Eo zaman garanti böylece formu getirir koleksiyonundan değerleri, hepsi E ya da bazı alt sınıf olacak (yani uyumlu olduğunu). drainToYöntem olarak ifade etmeye çalışıyor içine koleksiyon unsuru türü olması gerekiyor bu yüzden, koleksiyon E veya bir üst sınıfı .

Örnek olarak, şöyle bir sınıf hiyerarşiniz olduğunu varsayalım:

Parent extends Object
Child extends Parent

ve a LinkedBlockingQueue<Parent>. Bu geçişi, List<Child>tüm öğeleri güvenli bir şekilde kopyalayacak bir yapıda oluşturabilirsiniz, çünkü her Childbiri bir üst öğedir. List<Object>Bazı öğelerle uyumlu olmayabileceği için içeri giremediniz Parent.

Aynı şekilde, bu kuyruğu bir ... içine atayabilirsiniz, List<Object>çünkü her Parentbiri bir Object... ama onu boşaltamazsınız List<Child>çünkü List<Child>tüm öğelerinin uyumlu olmasını bekler Child.


25
+1. Bu gerçekten pratik fark. getirmek için genişletir, eklemek için süper.
Yishai

1
@Jon ilk paragrafta (her iki durumda da E'nin kendisi iyidir.) İle ne demek istiyorsun?
Geek

2
@Geek: Yani böyle bir şey varsa ? extends InputStreamveya ? super InputStreamo zaman bir InputStreamargüman olarak kullanabilirsiniz .
Jon Skeet

Josh Block'un etkili Java ile PECS açıklamasını hiçbir zaman gerçekten anlamadım. Ancak @Yishai, bu hatırlamak için yararlı bir yoldur. Belki yeni bir anımsatıcı önerebiliriz, SAGE: Super -> Ekle / Al -> Uzat
dcompiled

Eğer doğru okursam, "<? Uzatır E>" gerektirir "?" "E" nin alt sınıfıdır ve "<? super E>", "E" nin "?" alt sınıfı olmasını gerektirir, değil mi?
El Suscriptor Justiciero

130

Bunun nedenleri Java'nın jenerikleri nasıl uyguladığına dayanmaktadır.

Bir Diziler Örneği

Dizilerle bunu yapabilirsiniz (diziler kovaryanttır)

Integer[] myInts = {1,2,3,4};
Number[] myNumber = myInts;

Ancak, bunu yapmaya çalışırsanız ne olur?

myNumber[0] = 3.14; //attempt of heap pollution

Bu son satır iyi derlenebilir, ancak bu kodu çalıştırırsanız, bir ArrayStoreException. Çünkü bir tamsayı dizisine bir çift koymaya çalışıyorsunuz (sayı başvurusundan erişilmesine bakılmaksızın).

Bu, derleyiciyi kandırabileceğiniz anlamına gelir, ancak çalışma zamanı türü sistemini kandıramazsınız. Bu böyledir çünkü diziler yeniden kullanılabilir tipler olarak adlandırdığımız şeydir . Bu, çalışma zamanında Java'nın bu dizinin gerçekte bir tür başvurusu aracılığıyla erişilen bir tamsayılar dizisi olarak başlatıldığını bildiği anlamına gelir Number[].

Gördüğünüz gibi, bir şey nesnenin gerçek türü ve başka bir şey ona erişmek için kullandığınız referansın türü, değil mi?

Java Generics ile İlgili Sorun

Java genel türleriyle ilgili sorun, tür bilgisinin derleyici tarafından atılması ve çalışma zamanında kullanılamamasıdır. Bu işleme tür silme adı verilir . Java'da böyle jenerikleri uygulamak için iyi bir neden vardır, ancak bu uzun bir hikaye ve diğer şeylerin yanı sıra, önceden var olan kodla ikili uyumluluk ile yapılması gerekir (bkz . Sahip olduğumuz jenerikleri nasıl elde ettik ).

Ancak buradaki önemli nokta, çalışma zamanında tür bilgisi olmadığı için yığın kirliliğini sağlamadığımızdan emin olmamızın bir yolu yoktur.

Örneğin,

List<Integer> myInts = new ArrayList<Integer>();
myInts.add(1);
myInts.add(2);

List<Number> myNums = myInts; //compiler error
myNums.add(3.14); //heap pollution

Java derleyicisi bunu yapmanızı engellemezse, çalışma zamanı türü sistemi de sizi durduramaz, çünkü çalışma zamanında, bu listenin yalnızca tamsayıların bir listesi olması gerektiğini belirlemenin bir yolu yoktur. Java çalışma zamanı, yalnızca tamsayı içermesi gerektiğinde, istediğiniz her şeyi bu listeye koymanıza izin verir, çünkü oluşturulduğunda, tamsayıların bir listesi olarak bildirilir.

Bu nedenle, Java tasarımcıları derleyiciyi kandıramayacağınızdan emin oldular. Derleyiciyi kandıramazsanız (dizilerde yapabileceğimiz gibi), çalışma zamanı türü sistemini de kandıramazsınız.

Bu itibarla , jenerik tiplerin yeniden kullanılamaz olduğunu söylüyoruz .

Açıkçası, bu polimorfizmi engelleyecektir. Aşağıdaki örneği düşünün:

static long sum(Number[] numbers) {
   long summation = 0;
   for(Number number : numbers) {
      summation += number.longValue();
   }
   return summation;
}

Şimdi şöyle kullanabilirsiniz:

Integer[] myInts = {1,2,3,4,5};
Long[] myLongs = {1L, 2L, 3L, 4L, 5L};
Double[] myDoubles = {1.0, 2.0, 3.0, 4.0, 5.0};

System.out.println(sum(myInts));
System.out.println(sum(myLongs));
System.out.println(sum(myDoubles));

Ancak, aynı kodu genel koleksiyonlarla uygulamaya çalışırsanız, başarılı olmazsınız:

static long sum(List<Number> numbers) {
   long summation = 0;
   for(Number number : numbers) {
      summation += number.longValue();
   }
   return summation;
}

Eğer denerseniz derleyici hatalar alacaktı ...

List<Integer> myInts = asList(1,2,3,4,5);
List<Long> myLongs = asList(1L, 2L, 3L, 4L, 5L);
List<Double> myDoubles = asList(1.0, 2.0, 3.0, 4.0, 5.0);

System.out.println(sum(myInts)); //compiler error
System.out.println(sum(myLongs)); //compiler error
System.out.println(sum(myDoubles)); //compiler error

Çözüm, kovaryans ve kontravaryans olarak bilinen Java jeneriklerinin iki güçlü özelliğini kullanmayı öğrenmektir.

Kovaryans

Kovaryans ile bir yapıdaki öğeleri okuyabilirsiniz, ancak içine hiçbir şey yazamazsınız. Bütün bunlar geçerli beyanlardır.

List<? extends Number> myNums = new ArrayList<Integer>();
List<? extends Number> myNums = new ArrayList<Float>();
List<? extends Number> myNums = new ArrayList<Double>();

Ve şunları okuyabilirsiniz myNums:

Number n = myNums.get(0); 

Gerçek liste ne olursa olsun, bir sayıya yükseltilebileceğinden emin olabilirsiniz (sayıyı genişleten her şeyden sonra bir sayıdır, değil mi?)

Ancak, kovaryant bir yapıya hiçbir şey koymanıza izin verilmez.

myNumst.add(45L); //compiler error

Java, genel yapıdaki nesnenin gerçek türünün ne olduğunu garanti edemediğinden buna izin verilmez. Number'ı genişleten herhangi bir şey olabilir, ancak derleyici emin olamaz. Böylece okuyabilirsiniz, ama yazamazsınız.

contravariance

Karşıtlık ile tam tersini yapabilirsiniz. Bir şeyleri genel bir yapıya koyabilirsiniz, ancak ondan okuyamazsınız.

List<Object> myObjs = new List<Object>();
myObjs.add("Luke");
myObjs.add("Obi-wan");

List<? super Number> myNums = myObjs;
myNums.add(10);
myNums.add(3.14);

Bu durumda, nesnenin gerçek doğası bir Nesneler Listesi'dir ve karşıtlık yoluyla, Temelde tüm sayıların ortak ataları olarak Nesne olduğu için Sayılar koyabilirsiniz. Bu nedenle, tüm Sayılar nesnedir ve bu nedenle bu geçerlidir.

Ancak, bir sayı alacağınızı varsayarak, bu karşıt varyanttan hiçbir şeyi güvenle okuyamazsınız.

Number myNum = myNums.get(0); //compiler-error

Gördüğünüz gibi, derleyici bu satırı yazmanıza izin veriyorsa, çalışma zamanında bir ClassCastException alırsınız.

Get / Put Prensibi

Bu nedenle, yalnızca bir yapının genel değerlerini almak istediğinizde kovaryans kullanın, yalnızca bir yapıya genel değerler koymak istediğinizde karşıtlık kullanın ve her ikisini de yapmak istediğinizde tam genel türü kullanın.

Elimdeki en iyi örnek, herhangi bir numarayı bir listeden başka bir listeye kopyalayan aşağıdaki gibidir. Yalnızca kaynaktan öğeleri alır ve yalnızca öğeleri hedefe koyar .

public static void copy(List<? extends Number> source, List<? super Number> target) {
    for(Number number : source) {
        target(number);
    }
}

Kovaryans ve karşıtlık güçleri sayesinde bu böyle bir vaka için çalışır:

List<Integer> myInts = asList(1,2,3,4);
List<Double> myDoubles = asList(3.14, 6.28);
List<Object> myObjs = new ArrayList<Object>();

copy(myInts, myObjs);
copy(myDoubles, myObjs);

27
Bu cevap en üste çıkmalıdır. Güzel açıklama.
Suresh Atta

1
@edwindalorzo, Contravariance altında düzeltmek istediğiniz küçük bir yazım hatası var. Diyorsun List<Object> myObjs = new List<Object();( >ikinci kapanış eksik Object).

Bu ince kavramların fantastik, kolay ve açık örnekleri!
db1234

Başkalarının bir şeyleri hatırlamasına yardımcı olmak için ekleyebileceğiniz bir şey. Bir süper sınıftan bir yöntemi çağırmak istediğinizde, kullanın super.methodName. Kullanırken <? super E>, superyöndeki bir şeyin aksine "yönde bir şey" anlamına gelir extends. Örnek: Objectolduğu superyönünde Number(bir süper sınıf için) ve Integeriçinde extends(yayılana yana yönde Number).
BrainStorm.exe

59

<? extends E>Eüst sınır olarak tanımlar : "Bu, için kullanılabilir E".

<? super E> tanımlar Ealt sınır olarak : " Ebuna dökülebilir."


6
Bu, gördüğüm farkın en basit / pratik özetlerinden biridir.
JAB

1
On yıllardır (OOP ile) "üst" ve "alt" kavramlarının içgüdüsel bir ters çevrilmesi ile mücadele ediyorum. Şiddeti! Bana göre, Objectnihai süper sınıf olarak pozisyonuna rağmen (ve UML veya benzer miras ağaçlarında dikey olarak çizilmiş) doğası gereği daha düşük bir sınıftır. Bir sürü denemeye rağmen bunu asla geri alamadım.

3
@ tgm1024 "süper sınıf" ve "alt sınıf" size çok fazla sorun vermelidir.
David Moles

@DavidMoles, neden? Söylediklerimi kesinlikle takip etmiyorsun . "süper sınıf", "süper kümeye" benzer; uzmanlık kavramı IS-A ilişkisi altında uygulanabilirlikte bir azalmadır. Elma IS-A Meyvesi. Meyve (üst sınıf), bir alt küme olarak Apple (alt sınıf) içeren bir üst kümedir. Sözlü ilişki gayet iyi. Söylediğim şey, "üst" ve "alt" öğelerinin, "üst küme" ve "alt kümeye" içsel eşleşmeleri olduğu fikridir. Üst ve Alt OO terimleri olarak kullanılmamalıdır.

1
@ tgm1024 "Süper-" Latince süper ", üstü" ve "alt-" Latin altından " alt, " altından "gelir. Yani etimolojik olarak süper yukarı ve alt aşağı.
David Moles

12

Bunu cevaplamaya çalışacağım. Ama gerçekten iyi bir cevap almak için Joshua Bloch'un Effective Java (2. Baskı) kitabını kontrol etmelisiniz. "Producer Extends, Consumer Super" anlamına gelen anımsatıcı PECS'yi anlatıyor.

Fikir, nesneden genel değerleri kodluyorsanız, extends kullanmanız gerektiğidir. ancak genel tür için yeni değerler üretiyorsanız, super kullanmanız gerekir.

Yani mesela:

public void pushAll(Iterable<? extends E> src) {
  for (E e: src) 
    push(e);
}

Ve

public void popAll(Collection<? super E> dst) {
  while (!isEmpty())
    dst.add(pop())
}

Ama gerçekten bu kitaba bakmalısın: http://java.sun.com/docs/books/effective/


12

<? super E> anlamına geliyor any object including E that is parent of E

<? extends E> anlamına geliyor any object including E that is child of E .


kısa ve tatlı bir cevap.
chirag soni

7

Sen belki terimler için google istediğiniz contravariance ( <? super E>) ve kovaryans ( <? extends E>). Jenerikleri kavrarken en yararlı şeyin, yöntem imzasını anlamam benim için olduğunu buldum Collection.addAll:

public interface Collection<T> {
    public boolean addAll(Collection<? extends T> c);
}

Sadece bir eklemek mümkün olmak istiyorum olarak Stringbir karşı List<Object>:

List<Object> lo = ...
lo.add("Hello")

Ayrıca, yöntemle bir List<String>(veya herhangi bir koleksiyon String) ekleyebilmeniz gerekir addAll:

List<String> ls = ...
lo.addAll(ls)

Bununla birlikte, a List<Object>ve List<String>a'nın eşdeğer olmadığını ve ikincisinin de bir alt sınıfının alt sınıf olmadığını fark etmelisiniz . Gerekli olan, kovaryant tip parametre - yani <? extends T>bit kavramıdır .

Bunu yaptıktan sonra, karşıtlık da istediğiniz senaryoları düşünmek kolaydır ( Comparablearayüzü kontrol edin ).


4

Cevaptan önce; Lütfen açık olun

  1. Jenerikler sadece TYPE_SAFETY sağlamak için zaman özelliğini derlemek, RUNTIME boyunca mevcut olmayacak.
  2. Yalnızca Generics ile yapılan bir başvuru tür güvenliğini zorlar; referans jenerikler ile beyan edilmezse, emniyet tipi olmadan çalışacaktır.

Misal:

List stringList = new ArrayList<String>();
stringList.add(new Integer(10)); // will be successful.

Umarım bu joker karakteri daha net anlamanıza yardımcı olur.

//NOTE CE - Compilation Error
//      4 - For

class A {}

class B extends A {}

public class Test {

    public static void main(String args[]) {

        A aObj = new A();
        B bObj = new B();

        //We can add object of same type (A) or its subType is legal
        List<A> list_A = new ArrayList<A>();
        list_A.add(aObj);
        list_A.add(bObj); // A aObj = new B(); //Valid
        //list_A.add(new String()); Compilation error (CE);
        //can't add other type   A aObj != new String();


        //We can add object of same type (B) or its subType is legal
        List<B> list_B = new ArrayList<B>();
        //list_B.add(aObj); CE; can't add super type obj to subclass reference
        //Above is wrong similar like B bObj = new A(); which is wrong
        list_B.add(bObj);



        //Wild card (?) must only come for the reference (left side)
        //Both the below are wrong;   
        //List<? super A> wildCard_Wrongly_Used = new ArrayList<? super A>();
        //List<? extends A> wildCard_Wrongly_Used = new ArrayList<? extends A>();


        //Both <? extends A>; and <? super A> reference will accept = new ArrayList<A>
        List<? super A> list_4__A_AND_SuperClass_A = new ArrayList<A>();
                        list_4__A_AND_SuperClass_A = new ArrayList<Object>();
                      //list_4_A_AND_SuperClass_A = new ArrayList<B>(); CE B is SubClass of A
                      //list_4_A_AND_SuperClass_A = new ArrayList<String>(); CE String is not super of A  
        List<? extends A> list_4__A_AND_SubClass_A = new ArrayList<A>();
                          list_4__A_AND_SubClass_A = new ArrayList<B>();
                        //list_4__A_AND_SubClass_A = new ArrayList<Object>(); CE Object is SuperClass of A


        //CE; super reference, only accepts list of A or its super classes.
        //List<? super A> list_4__A_AND_SuperClass_A = new ArrayList<String>(); 

        //CE; extends reference, only accepts list of A or its sub classes.
        //List<? extends A> list_4__A_AND_SubClass_A = new ArrayList<Object>();

        //With super keyword we can use the same reference to add objects
        //Any sub class object can be assigned to super class reference (A)                  
        list_4__A_AND_SuperClass_A.add(aObj);
        list_4__A_AND_SuperClass_A.add(bObj); // A aObj = new B();
        //list_4__A_AND_SuperClass_A.add(new Object()); // A aObj != new Object(); 
        //list_4__A_AND_SuperClass_A.add(new String()); CE can't add other type

        //We can't put anything into "? extends" structure. 
        //list_4__A_AND_SubClass_A.add(aObj); compilation error
        //list_4__A_AND_SubClass_A.add(bObj); compilation error
        //list_4__A_AND_SubClass_A.add("");   compilation error

        //The Reason is below        
        //List<Apple> apples = new ArrayList<Apple>();
        //List<? extends Fruit> fruits = apples;
        //fruits.add(new Strawberry()); THIS IS WORNG :)

        //Use the ? extends wildcard if you need to retrieve object from a data structure.
        //Use the ? super wildcard if you need to put objects in a data structure.
        //If you need to do both things, don't use any wildcard.


        //Another Solution
        //We need a strong reference(without wild card) to add objects 
        list_A = (ArrayList<A>) list_4__A_AND_SubClass_A;
        list_A.add(aObj);
        list_A.add(bObj);

        list_B = (List<B>) list_4__A_AND_SubClass_A;
        //list_B.add(aObj); compilation error
        list_B.add(bObj);

        private Map<Class<? extends Animal>, List<? extends Animal>> animalListMap;

        public void registerAnimal(Class<? extends Animal> animalClass, Animal animalObject) {

            if (animalListMap.containsKey(animalClass)) {
                //Append to the existing List
                 /*    The ? extends Animal is a wildcard bounded by the Animal class. So animalListMap.get(animalObject);
                 could return a List<Donkey>, List<Mouse>, List<Pikachu>, assuming Donkey, Mouse, and Pikachu were all sub classes of Animal. 
                 However, with the wildcard, you are telling the compiler that you don't care what the actual type is as long as it is a sub type of Animal.      
                 */   
                //List<? extends Animal> animalList = animalListMap.get(animalObject);
                //animalList.add(animalObject);  //Compilation Error because of List<? extends Animal>
                List<Animal> animalList = animalListMap.get(animalObject);
                animalList.add(animalObject);      


            } 
    }

    }
}


Genel Sınıf türü
konusundaki cevabım

1
Kod ile iyi açıklama. Ancak kod bloğunun dışındaki kodda yorumlar kullandıysanız, görüntülemek için daha iyi ve daha okunabilir olurdu.
Prabhu

3

Üst sınırı olan bir joker karakter "? Genişletir" gibi görünür ve Type, type Type dahil olmak üzere Type alt tipleri olan tüm türlerin ailesini belirtir. Tip, üst sınır olarak adlandırılır.

Alt sınırı olan bir joker karakter "? Super Type" gibi görünür ve Type, type Type dahil olmak üzere tüm Type türlerinin ailesini ifade eder. Tip alt sınır olarak adlandırılır.


1

Ebeveyn sınıfınız ve Ebeveyn sınıfından miras alınan bir Child sınıfınız var. Ebeveyn Sınıfı, GrandParent Sınıfı adı verilen başka bir sınıftan miras alınır.Yani miras sırası GrandParent> Ebeveyn> Çocuk'tur. Şimdi, <? Ana sınıfı genişletir - - Ana sınıfı veya Çocuk sınıfını <? super Parent> - Bu, Ebeveyn sınıfını veya GrandParent sınıfını kabul eder

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.