Apa perbedaan antara <? super E> dan <? memanjang E>?


147

Apa perbedaan antara <? super E>dan <? extends E>?

Misalnya ketika Anda melihat kelas java.util.concurrent.LinkedBlockingQueueada tanda tangan berikut untuk konstruktor:

public LinkedBlockingQueue(Collection<? extends E> c)

dan satu untuk metode:

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

Jawaban:


182

Yang pertama mengatakan bahwa itu "beberapa jenis yang merupakan leluhur E"; yang kedua mengatakan bahwa "beberapa jenis yang merupakan subkelas E". (Dalam kedua kasus E itu sendiri tidak masalah.)

Jadi konstruktor menggunakan ? extends Eformulir sehingga menjamin bahwa ketika mengambil nilai dari koleksi, mereka semua akan menjadi E atau beberapa subkelas (yaitu kompatibel). The drainToMetode berusaha untuk menempatkan nilai-nilai ke dalam koleksi, sehingga koleksi harus memiliki jenis unsur E atau superclass .

Sebagai contoh, misalkan Anda memiliki hierarki kelas seperti ini:

Parent extends Object
Child extends Parent

dan a LinkedBlockingQueue<Parent>. Anda dapat membuat passing ini dalam List<Child>yang akan menyalin semua elemen dengan aman, karena setiap Childorang tua. Anda tidak dapat memasukkan a List<Object>karena beberapa elemen mungkin tidak kompatibel Parent.

Demikian juga Anda dapat menguras antrian itu menjadi List<Object> karena setiap Parentadalah Object... tetapi Anda tidak dapat mengalirkannya ke dalam List<Child>karena List<Child>mengharapkan semua elemennya kompatibel dengan Child.


25
+1. Itu benar-benar perbedaan praktis. meluas hingga menjemput, super untuk disisipkan.
Yishai

1
@ Jon Apa yang Anda maksud dengan (Dalam kedua kasus E itu sendiri tidak apa-apa.) Di paragraf pertama?
Geek

2
@ Geek: Maksud saya jika Anda memiliki sesuatu seperti ? extends InputStreamatau ? super InputStreamkemudian Anda dapat menggunakan InputStreamargumen.
Jon Skeet

Saya tidak pernah mendapatkan penjelasan PECS dari Josh Block di Java yang efektif. Namun @Yishai, ini adalah cara yang berguna untuk diingat. Mungkin kita bisa mengusulkan mnemonic baru, SAGE: Super -> Add / Get -> Extend
dcompiled

Jadi jika saya membacanya dengan benar, "<? Extends E>" memerlukan itu "?" adalah subclass dari "E", dan "<? super E>" mensyaratkan bahwa "E" adalah subclass dari "?", kan?
El Suscriptor Justiciero

130

Alasan untuk ini didasarkan pada bagaimana Java mengimplementasikan obat generik.

Contoh Array

Dengan array, Anda dapat melakukan ini (array bersifat kovarian)

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

Tetapi, apa yang akan terjadi jika Anda mencoba melakukan ini?

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

Baris terakhir ini akan mengkompilasi dengan baik, tetapi jika Anda menjalankan kode ini, Anda bisa mendapatkan ArrayStoreException. Karena Anda mencoba memasukkan dobel ke dalam array integer (terlepas dari diakses melalui referensi nomor).

Ini berarti bahwa Anda dapat menipu kompilator, tetapi Anda tidak dapat menipu sistem tipe runtime. Dan ini terjadi karena array adalah apa yang kita sebut tipe yang dapat diverifikasi . Ini berarti bahwa pada saat runtime Java tahu bahwa array ini sebenarnya dipakai sebagai array integer yang kebetulan diakses melalui referensi tipe Number[].

Jadi, seperti yang Anda lihat, satu hal adalah jenis objek yang sebenarnya, dan satu hal lagi adalah jenis referensi yang Anda gunakan untuk mengaksesnya, bukan?

Masalah dengan Java Generics

Sekarang, masalah dengan tipe generik Java adalah bahwa informasi tipe dibuang oleh kompiler dan tidak tersedia pada saat run time. Proses ini disebut tipe erasure . Ada alasan bagus untuk mengimplementasikan obat generik seperti ini di Jawa, tetapi itu adalah cerita yang panjang, dan itu harus dilakukan, antara lain, dengan kompatibilitas biner dengan kode yang sudah ada sebelumnya (lihat Bagaimana kami mendapatkan obat generik yang kami miliki ).

Tetapi poin penting di sini adalah karena, pada saat runtime tidak ada informasi jenis, tidak ada cara untuk memastikan bahwa kami tidak melakukan polusi timbunan.

Misalnya,

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

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

Jika kompiler Java tidak menghentikan Anda dari melakukan ini, sistem tipe runtime tidak dapat menghentikan Anda juga, karena tidak ada cara, pada saat runtime, untuk menentukan bahwa daftar ini seharusnya menjadi daftar bilangan bulat saja. Java runtime akan membiarkan Anda memasukkan apa pun yang Anda inginkan ke dalam daftar ini, padahal seharusnya hanya berisi bilangan bulat, karena ketika dibuat, itu dinyatakan sebagai daftar bilangan bulat.

Dengan demikian, para perancang Java memastikan bahwa Anda tidak dapat membodohi kompiler. Jika Anda tidak dapat membodohi kompiler (seperti yang dapat kita lakukan dengan array), Anda juga tidak dapat mengelabui sistem tipe runtime.

Karena itu, kami mengatakan bahwa tipe generik tidak dapat diverifikasi .

Jelas, ini akan menghambat polimorfisme. Perhatikan contoh berikut:

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

Sekarang Anda bisa menggunakannya seperti ini:

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));

Tetapi jika Anda mencoba menerapkan kode yang sama dengan koleksi umum, Anda tidak akan berhasil:

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

Anda akan mendapatkan eril kompiler jika Anda mencoba ...

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

Solusinya adalah belajar menggunakan dua fitur kuat dari generik Java yang dikenal sebagai kovarians dan contravariance.

Kovarian

Dengan kovarian Anda dapat membaca item dari suatu struktur, tetapi Anda tidak dapat menulis apa pun di dalamnya. Semua ini adalah deklarasi yang valid.

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

Dan Anda dapat membaca dari myNums:

Number n = myNums.get(0); 

Karena Anda dapat yakin bahwa apa pun yang ada dalam daftar aktual, daftar itu dapat disebarluaskan ke suatu Angka (setelah semua hal yang meluas Angka adalah suatu Angka, bukan?)

Namun, Anda tidak diizinkan memasukkan apa pun ke dalam struktur kovarian.

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

Ini tidak akan diizinkan, karena Java tidak dapat menjamin apa jenis objek yang sebenarnya dalam struktur generik. Itu bisa berupa apa saja yang meluas Bilangan, tetapi kompiler tidak dapat memastikan. Jadi Anda bisa membaca, tetapi tidak menulis.

Contravariance

Dengan contravariance Anda bisa melakukan yang sebaliknya. Anda dapat meletakkan berbagai hal dalam struktur umum, tetapi Anda tidak dapat membacanya.

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);

Dalam hal ini, sifat sebenarnya dari objek adalah Daftar Objek, dan melalui contravariance, Anda dapat memasukkan Angka ke dalamnya, pada dasarnya karena semua angka memiliki Objek sebagai leluhur bersama mereka. Dengan demikian, semua Bilangan adalah objek, dan karenanya ini valid.

Namun, Anda tidak dapat dengan aman membaca apa pun dari struktur contravarian ini dengan asumsi Anda akan mendapatkan nomor.

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

Seperti yang Anda lihat, jika kompiler mengizinkan Anda untuk menulis baris ini, Anda akan mendapatkan ClassCastException saat runtime.

Prinsip Get / Put

Dengan demikian, gunakan kovarians ketika Anda hanya bermaksud untuk mengambil nilai-nilai generik dari suatu struktur, gunakan contravariance ketika Anda hanya bermaksud untuk memasukkan nilai-nilai generik ke dalam struktur dan menggunakan tipe generik yang tepat ketika Anda ingin melakukan keduanya.

Contoh terbaik yang saya miliki adalah yang berikut ini yang menyalin segala jenis angka dari satu daftar ke daftar lain. Itu hanya mendapatkan item dari sumber, dan itu hanya menempatkan item di target.

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

Berkat kekuatan kovarians dan contravariance, ini berfungsi untuk kasus seperti ini:

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
Jawaban ini harus naik ke atas. Penjelasan yang bagus.
Suresh Atta

1
@edwindalorzo, ada kesalahan ketik kecil yang ingin Anda perbaiki di bawah Contravariance. Anda mengatakan List<Object> myObjs = new List<Object();(yang tidak ada penutupan >untuk yang kedua Object).

Contoh fantastis, mudah, dan jelas dari konsep-konsep halus ini!
db1234

Sesuatu yang bisa Anda tambahkan untuk membantu orang lain mengingat berbagai hal. Saat Anda ingin memanggil metode dari kelas super, Anda gunakan super.methodName. Saat menggunakan <? super E>, itu berarti "sesuatu dalam superarah" sebagai kebalikan dari sesuatu dalam extendsarah. Contoh: Objectada di superarah Number(karena itu adalah kelas super) dan Integerberada di extendsarah (karena meluas Number).
BrainStorm.exe

59

<? extends E>mendefinisikan Esebagai batas atas: "Ini dapat dilemparkan ke E".

<? super E>mendefinisikan Esebagai batas bawah: " Edapat digunakan untuk ini."


6
Ini adalah salah satu ringkasan sederhana / praktis terbaik dari perbedaan yang pernah saya lihat.
JAB

1
Selama beberapa dekade sekarang (dengan OOP) saya telah melawan inversi naluriah tentang "atas" dan "rendah". Memburuk! Bagi saya, Objectsecara inheren kelas bawah, meskipun posisinya sebagai superclass tertinggi (dan secara vertikal digambar dalam UML atau pohon pewarisan serupa). Saya tidak pernah bisa membatalkan ini meskipun sudah ribuan tahun mencoba.

3
@ tgm1024 "superclass" dan "subclass" harus memberi Anda banyak masalah.
David Moles

@ Davidvidoles, mengapa? Anda jelas tidak mengikuti apa yang saya katakan sama sekali. "superclass" mirip dengan "superset"; Gagasan spesialisasi adalah pengurangan penerapan di bawah hubungan IS-A. Apple IS-A Fruit. Buah (superclass) adalah superset termasuk Apple (subclass) sebagai subset. Hubungan verbal itu baik-baik saja. Apa yang saya katakan rusak adalah gagasan bahwa "atas" dan "lebih rendah" memiliki pemetaan intrinsik ke "superset" dan "subset". Atas dan Bawah harus dihindari sebagai istilah dalam OO.

1
@ tgm1024 "Super-" berasal dari super Latin "atas, di", dan "sub-" dari sub Latin "di bawah, di bawah". Artinya, secara etimologis, super naik dan sub turun.
David Moles

12

Saya akan mencoba dan menjawab ini. Tetapi untuk mendapatkan jawaban yang benar-benar bagus Anda harus memeriksa buku Joshua Bloch, Effective Java (2nd Edition). Dia menggambarkan Pecs mnemonik, yang merupakan singkatan dari "Produser Extends, Consumer Super".

Idenya adalah bahwa jika Anda menggunakan kode nilai generik dari objek maka Anda harus menggunakan extends. tetapi jika Anda menghasilkan nilai baru untuk tipe generik Anda harus menggunakan super.

Jadi misalnya:

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

Dan

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

Tapi sungguh Anda harus memeriksa buku ini: http://java.sun.com/docs/books/effective/


12

<? super E> cara any object including E that is parent of E

<? extends E> cara any object including E that is child of E .


jawaban singkat dan manis.
chirag soni

7

Anda mungkin ingin google untuk istilah contravariance ( <? super E>) dan covariance ( <? extends E>). Saya menemukan bahwa hal yang paling berguna ketika memahami obat generik adalah bagi saya untuk memahami metode tanda tangan Collection.addAll:

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

Sama seperti Anda ingin dapat menambahkan Stringke List<Object>:

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

Anda juga harus dapat menambahkan List<String>(atau koleksi Strings) melalui addAllmetode:

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

Namun Anda harus menyadari bahwa a List<Object>dan a List<String>tidak setara dan tidak juga yang terakhir adalah subclass dari yang pertama. Apa yang dibutuhkan adalah konsep parameter tipe kovarian - yaitu <? extends T>bit.

Setelah Anda memiliki ini, mudah untuk memikirkan skenario di mana Anda ingin contravariance juga (periksa Comparableantarmuka).


4

Sebelum jawabannya; Harap jelaskan itu

  1. Generik hanya mengkompilasi fitur waktu untuk memastikan TYPE_SAFETY, itu tidak akan tersedia selama RUNTIME.
  2. Hanya referensi dengan Generics yang akan memaksakan keamanan jenis; jika referensi tidak dideklarasikan dengan obat generik maka itu akan berfungsi tanpa tipe keselamatan.

Contoh:

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

Semoga ini bisa membantu Anda memahami wildcard lebih jelas.

//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);      


            } 
    }

    }
}



1
Penjelasan bagus dengan kode. Tetapi jika Anda menggunakan komentar dalam kode di luar blok kode, akan lebih baik untuk dilihat dan lebih mudah dibaca.
Prabhu

3

Wildcard dengan batas atas terlihat seperti "? Extends Type" dan singkatan dari semua jenis subtipe Type, tipe Type yang disertakan. Jenis ini disebut batas atas.

Wildcard dengan batas bawah terlihat seperti "? Super Type" dan singkatan dari keluarga semua jenis yang supertypes dari Type, type Type yang disertakan. Jenis ini disebut batas bawah.


1

Anda memiliki kelas Induk dan kelas Anak yang diwarisi dari kelas Induk. Kelas Induk diwarisi dari kelas lain yang disebut Kelas GrandParent. Jadi urutan pewarisannya adalah GrandParent> Parent> Child. Sekarang, <? extends Parent> - Ini menerima kelas Induk atau kelas Anak <? super Parent> - Ini menerima kelas Induk atau kelas GrandParent

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.