Daftar <Peta <String, String >> vs List <? extends Map <String, String >>

129

Apakah ada perbedaan di antara keduanya

List<Map<String, String>>

dan

List<? extends Map<String, String>>

?

Jika tidak ada perbedaan, apa manfaat menggunakan ? extends?

Eng.Fouad
sumber
2
Saya suka java tetapi ini adalah salah satu hal yang tidak begitu baik ...
Mite Mitreski
4
Saya merasa bahwa jika kita membacanya seperti "apa pun yang meluas ...", itu menjadi jelas.
Sampah
Luar biasa, lebih dari 12 ribu tampilan dalam waktu sekitar 3 hari? !!
Eng.Fouad
5
Itu mencapai halaman depan Berita Hacker. Selamat.
r3st0r3
1
@Eng. Temukan di sini. Tidak lagi di halaman depan, tetapi ada di sana kemarin. news.ycombinator.net/item?id=3751901 (kemarin adalah pertengahan Minggu di India).
r3st0r3

Jawaban:

180

Perbedaannya adalah bahwa, misalnya, a

List<HashMap<String,String>>

adalah

List<? extends Map<String,String>>

tapi tidak

List<Map<String,String>>

Begitu:

void withWilds( List<? extends Map<String,String>> foo ){}
void noWilds( List<Map<String,String>> foo ){}

void main( String[] args ){
    List<HashMap<String,String>> myMap;

    withWilds( myMap ); // Works
    noWilds( myMap ); // Compiler error
}

Anda akan berpikir a Listdari HashMapseharusnya a Listdari Maps, tetapi ada alasan bagus mengapa itu tidak:

Misalkan Anda bisa melakukan:

List<HashMap<String,String>> hashMaps = new ArrayList<HashMap<String,String>>();

List<Map<String,String>> maps = hashMaps; // Won't compile,
                                          // but imagine that it could

Map<String,String> aMap = Collections.singletonMap("foo","bar"); // Not a HashMap

maps.add( aMap ); // Perfectly legal (adding a Map to a List of Maps)

// But maps and hashMaps are the same object, so this should be the same as

hashMaps.add( aMap ); // Should be illegal (aMap is not a HashMap)

Jadi ini adalah mengapa Listdari HashMaps seharusnya tidak menjadi Listdari Maps.

kebenaran
sumber
6
Tetap saja, HashMapini Mapkarena polimorfisme.
Eng.Fouad
46
Benar, tetapi Listdari HashMaps bukan Listdari Maps.
trutheality
1
Ini disebut kuantifikasi Terikat
Dan Burton
Contoh yang baik. Juga perlu dicatat adalah bahwa bahkan jika Anda menyatakan List<Map<String,String>> maps = hashMaps; dan HashMap<String,String> aMap = new HashMap<String, String>();, Anda masih akan menemukan bahwa maps.add(aMap);itu ilegal sementara hashMaps.add(aMap);itu legal. Tujuannya adalah untuk mencegah penambahan jenis yang salah, tetapi itu tidak akan memungkinkan penambahan jenis yang benar (kompiler tidak dapat menentukan jenis "benar" selama waktu kompilasi)
Raze
@Raze tidak, sebenarnya Anda dapat menambahkan HashMapke daftar Maps, kedua contoh Anda legal, jika saya membacanya dengan benar.
trutheality
24

Anda tidak dapat menetapkan ekspresi dengan tipe seperti List<NavigableMap<String,String>>yang pertama.

(Jika Anda ingin tahu mengapa Anda tidak dapat menetapkan List<String>untuk List<Object>melihat jutaan pertanyaan lain di SO.)

Tom Hawtin - tackline
sumber
1
dapat menjelaskan lebih lanjut? saya tidak mengerti atau tautan apa pun untuk praktik yang baik?
Samir Mangroliya
3
@ Samir Jelaskan apa? List<String>bukan subtipe dari List<Object>? - lihat, misalnya, stackoverflow.com/questions/3246137/…
Tom Hawtin - tackline
2
Ini tidak menjelaskan apa perbedaan antara dengan atau tanpa ? extends. Juga tidak menjelaskan korelasi dengan super / subtipe atau co / contravariance (jika ada).
Abel
16

Apa yang saya lewatkan dalam jawaban lain adalah referensi tentang bagaimana ini berhubungan dengan co-dan contravariance dan sub- dan supertypes (yaitu, polimorfisme) secara umum dan ke Jawa pada khususnya. Ini mungkin dipahami dengan baik oleh OP, tetapi untuk berjaga-jaga, ini dia:

Kovarian

Jika Anda memiliki kelas Automobile, maka Cardan Truckadalah subtipe mereka. Setiap Mobil dapat ditugaskan ke variabel tipe Mobil, ini terkenal di OO dan disebut polimorfisme. Kovarian mengacu pada penggunaan prinsip yang sama ini dalam skenario dengan obat generik atau delegasi. Java belum memiliki delegasi, jadi istilah ini hanya berlaku untuk obat generik.

Saya cenderung menganggap kovarian sebagai polimorfisme standar apa yang Anda harapkan untuk bekerja tanpa berpikir, karena:

List<Car> cars;
List<Automobile> automobiles = cars;
// You'd expect this to work because Car is-a Automobile, but
// throws inconvertible types compile error.

Alasan kesalahan adalah, bagaimanapun, benar: List<Car> tidak mewarisi dari List<Automobile>dan dengan demikian tidak dapat ditugaskan satu sama lain. Hanya parameter tipe generik yang memiliki hubungan bawaan. Orang mungkin berpikir bahwa kompiler Java tidak cukup pintar untuk benar memahami skenario Anda di sana. Namun, Anda dapat membantu kompiler dengan memberinya petunjuk:

List<Car> cars;
List<? extends Automobile> automobiles = cars;   // no error

Contravariance

Kebalikan dari co-variance adalah contravariance. Di mana dalam kovarians tipe parameter harus memiliki hubungan subtipe, sebaliknya mereka harus memiliki hubungan supertipe. Ini dapat dianggap sebagai warisan batas atas: supertipe apa pun diizinkan dan termasuk jenis yang ditentukan:

class AutoColorComparer implements Comparator<Automobile>
    public int compare(Automobile a, Automobile b) {
        // Return comparison of colors
    }

Ini dapat digunakan dengan Collections.sort :

public static <T> void sort(List<T> list, Comparator<? super T> c)

// Which you can call like this, without errors:
List<Car> cars = getListFromSomewhere();
Collections.sort(cars, new AutoColorComparer());

Anda bahkan bisa menyebutnya dengan pembanding yang membandingkan objek dan menggunakannya dengan jenis apa pun.

Kapan menggunakan kontra atau ko-varians?

Mungkin sedikit OT, Anda tidak bertanya, tetapi membantu memahami menjawab pertanyaan Anda. Secara umum, ketika Anda mendapatkan sesuatu, gunakan kovarians dan ketika Anda meletakkan sesuatu, gunakan contravariance. Ini paling baik dijelaskan dalam jawaban untuk pertanyaan Stack Overflow. Bagaimana contravariance digunakan dalam Java generics? .

Jadi dengan apa itu List<? extends Map<String, String>>

Anda menggunakan extends, jadi aturan untuk kovarians berlaku. Di sini Anda memiliki daftar peta dan setiap item yang Anda simpan dalam daftar harus a Map<string, string>atau berasal dari itu. Pernyataan List<Map<String, String>>tidak dapat diturunkan Map, tetapi harus berupa a Map .

Karenanya, berikut ini akan berfungsi, karena TreeMapmewarisi dari Map:

List<Map<String, String>> mapList = new ArrayList<Map<String, String>>();
mapList.add(new TreeMap<String, String>());

tetapi ini tidak akan:

List<? extends Map<String, String>> mapList = new ArrayList<? extends Map<String, String>>();
mapList.add(new TreeMap<String, String>());

dan ini juga tidak akan berhasil, karena tidak memenuhi batasan kovarian:

List<? extends Map<String, String>> mapList = new ArrayList<? extends Map<String, String>>();
mapList.add(new ArrayList<String>());   // This is NOT allowed, List does not implement Map

Apa lagi?

Ini mungkin jelas, tetapi Anda mungkin telah mencatat bahwa menggunakan extendskata kunci hanya berlaku untuk parameter itu dan tidak untuk sisanya. Yaitu, yang berikut ini tidak akan dikompilasi:

List<? extends Map<String, String>> mapList = new List<? extends Map<String, String>>();
mapList.add(new TreeMap<String, Element>())  // This is NOT allowed

Misalkan Anda ingin mengizinkan jenis apa pun di peta, dengan kunci sebagai string, Anda dapat menggunakan extendpada setiap parameter tipe. Yaitu, misalkan Anda memproses XML dan Anda ingin menyimpan AttrNode, Elemen dll di peta, Anda dapat melakukan sesuatu seperti:

List<? extends Map<String, ? extends Node>> listOfMapsOfNodes = new...;

// Now you can do:
listOfMapsOfNodes.add(new TreeMap<Sting, Element>());
listOfMapsOfNodes.add(new TreeMap<Sting, CDATASection>());
Abel
sumber
Tidak ada dalam "Jadi dengan apa ..." akan dikompilasi.
NobleUplift
@NobleUplift: sulit untuk membantu Anda jika Anda tidak memberikan pesan kesalahan yang Anda dapatkan. Pertimbangkan juga, sebagai alternatif, untuk mengajukan pertanyaan baru pada SO untuk mendapatkan jawaban Anda, peluang sukses yang lebih besar. Kode di atas hanya potongan, tergantung Anda menerapkannya dalam skenario Anda.
Abel
Saya tidak punya pertanyaan baru, saya punya perbaikan. List<? extends Map<String, String>> mapList = new ArrayList<? extends Map<String, String>>(); mapList.add(new TreeMap<String, String>());hasil dalam found: ? extends java.util.Map<java.lang.String,java.lang.String> required: class or interface without bounds. List<Map<String, String>> mapList = new ArrayList<Map<String, String>>(); mapList.add(new TreeMap<String, String>());bekerja dengan sempurna. Contoh terakhir jelas benar.
NobleUplift
@NobleUplift: maaf, bepergian lama, akan diperbaiki ketika saya kembali, dan terima kasih telah menunjukkan kesalahan mencolok! :)
Abel
Tidak masalah, saya hanya senang itu akan diperbaiki. Saya melakukan pengeditan untuk Anda jika Anda ingin menyetujuinya.
NobleUplift
4

Hari ini, saya telah menggunakan fitur ini, jadi inilah contoh kehidupan nyata saya yang sangat segar. (Saya telah mengubah nama kelas dan metode menjadi yang umum sehingga mereka tidak akan mengalihkan perhatian dari titik yang sebenarnya.)

Saya memiliki metode yang dimaksudkan untuk menerima Setdari Abenda-benda yang saya awalnya menulis dengan tanda tangan ini:

void myMethod(Set<A> set)

Tetapi ingin benar-benar menyebutnya dengan Sets dari subclass A. Tapi ini tidak diizinkan! (Alasannya adalah, myMethodbisa menambahkan objek ke settipe A, tetapi bukan dari subtipe setobjek yang dinyatakan berada di situs pemanggil. Jadi ini dapat merusak sistem tipe jika memungkinkan.)

Sekarang, inilah obat generik untuk menyelamatkan, karena berfungsi seperti yang dimaksudkan jika saya menggunakan metode tanda tangan ini sebagai gantinya:

<T extends A> void myMethod(Set<T> set)

atau lebih pendek, jika Anda tidak perlu menggunakan tipe aktual di tubuh metode:

void myMethod(Set<? extends A> set)

Dengan cara ini, settipe menjadi kumpulan objek dari subtipe aktual A, sehingga menjadi mungkin untuk menggunakan ini dengan subkelas tanpa membahayakan sistem tipe.

Rörd
sumber
0

Seperti yang Anda sebutkan, mungkin ada dua versi di bawah ini untuk mendefinisikan Daftar:

  1. List<? extends Map<String, String>>
  2. List<?>

2 sangat terbuka. Itu dapat menampung semua jenis objek. Ini mungkin tidak berguna jika Anda ingin memiliki peta jenis tertentu. Jika seseorang secara tidak sengaja meletakkan jenis peta yang berbeda, misalnya Map<String, int>,. Metode konsumen Anda mungkin rusak.

Untuk memastikan bahwa Listdapat menampung objek dari tipe tertentu, Java generics diperkenalkan ? extends. Jadi di # 1, Listbisa memegang objek apa pun yang berasal dari Map<String, String>tipe. Menambahkan jenis data apa pun akan membuat pengecualian.

Funsuk Wangadu
sumber