Apakah ada perbedaan di antara keduanya
List<Map<String, String>>
dan
List<? extends Map<String, String>>
?
Jika tidak ada perbedaan, apa manfaat menggunakan ? extends
?
Apakah ada perbedaan di antara keduanya
List<Map<String, String>>
dan
List<? extends Map<String, String>>
?
Jika tidak ada perbedaan, apa manfaat menggunakan ? extends
?
Jawaban:
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 List
dari HashMap
seharusnya a List
dari Map
s, 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 List
dari HashMap
s seharusnya tidak menjadi List
dari Map
s.
HashMap
ini Map
karena polimorfisme.
List
dari HashMap
s bukan List
dari Map
s.
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)
HashMap
ke daftar Map
s, kedua contoh Anda legal, jika saya membacanya dengan benar.
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.)
List<String>
bukan subtipe dari List<Object>
? - lihat, misalnya, stackoverflow.com/questions/3246137/…
? extends
. Juga tidak menjelaskan korelasi dengan super / subtipe atau co / contravariance (jika ada).
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:
Jika Anda memiliki kelas Automobile
, maka Car
dan Truck
adalah 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
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.
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? .
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 TreeMap
mewarisi 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
Ini mungkin jelas, tetapi Anda mungkin telah mencatat bahwa menggunakan extends
kata 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 extend
pada 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>());
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.
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 Set
dari A
benda-benda yang saya awalnya menulis dengan tanda tangan ini:
void myMethod(Set<A> set)
Tetapi ingin benar-benar menyebutnya dengan Set
s dari subclass A
. Tapi ini tidak diizinkan! (Alasannya adalah, myMethod
bisa menambahkan objek ke set
tipe A
, tetapi bukan dari subtipe set
objek 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, set
tipe menjadi kumpulan objek dari subtipe aktual A
, sehingga menjadi mungkin untuk menggunakan ini dengan subkelas tanpa membahayakan sistem tipe.
Seperti yang Anda sebutkan, mungkin ada dua versi di bawah ini untuk mendefinisikan Daftar:
List<? extends Map<String, String>>
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 List
dapat menampung objek dari tipe tertentu, Java generics diperkenalkan ? extends
. Jadi di # 1, List
bisa memegang objek apa pun yang berasal dari Map<String, String>
tipe. Menambahkan jenis data apa pun akan membuat pengecualian.