Apa alasan di balik keputusan untuk tidak memiliki metode get sepenuhnya generik di antarmuka java.util.Map<K, V>
.
Untuk memperjelas pertanyaan, tanda tangan dari metode ini adalah
V get(Object key)
dari pada
V get(K key)
dan saya bertanya-tanya mengapa (hal yang sama untuk remove, containsKey, containsValue
).
java
generics
collections
map
WMR
sumber
sumber
Jawaban:
Seperti yang disebutkan oleh orang lain, alasan mengapa
get()
, dll. Tidak umum karena kunci entri yang Anda ambil tidak harus sama dengan objek yang Anda lewatiget()
; spesifikasi metode hanya mensyaratkan bahwa mereka sama. Ini mengikuti dari bagaimanaequals()
metode mengambil dalam Objek sebagai parameter, bukan hanya tipe yang sama seperti objek.Meskipun mungkin secara umum benar bahwa banyak kelas telah
equals()
didefinisikan sehingga objeknya hanya bisa sama dengan objek dari kelasnya sendiri, ada banyak tempat di Jawa di mana hal ini tidak terjadi. Misalnya, spesifikasi untukList.equals()
mengatakan bahwa dua objek Daftar sama jika keduanya Daftar dan memiliki konten yang sama, bahkan jika mereka adalah implementasi yang berbeda dariList
. Jadi kembali ke contoh dalam pertanyaan ini, sesuai dengan spesifikasi metode ini mungkin untuk memilikiMap<ArrayList, Something>
dan bagi saya untuk memanggilget()
denganLinkedList
argumen sebagai, dan itu harus mengambil kunci yang merupakan daftar dengan konten yang sama. Ini tidak akan mungkin terjadi jikaget()
bersifat generik dan membatasi jenis argumennya.sumber
V Get(K k)
di C #?m.get(linkedList)
, mengapa Anda tidak mendefinisikanm
tipe sebagaiMap<List,Something>
? Saya tidak bisa memikirkan usecase di mana meneleponm.get(HappensToBeEqual)
tanpa mengubahMap
tipe untuk mendapatkan antarmuka masuk akal.TreeMap
mungkin gagal saat Anda meneruskan objek dengan tipe yang salah keget
metode tetapi dapat lewat sesekali, misalnya saat peta kosong. Dan bahkan lebih buruk, dalam kasus disediakanComparator
dalamcompare
metode (yang memiliki tanda tangan generik!) Bisa disebut dengan argumen dari tipe yang salah tanpa peringatan dicentang. Ini adalah perilaku yang rusak.Seorang programmer Java yang luar biasa di Google, Kevin Bourrillion, menulis tentang masalah ini dalam sebuah posting blog beberapa waktu lalu (diakui dalam konteks
Set
alih-alihMap
). Kalimat yang paling relevan:Saya tidak sepenuhnya yakin saya setuju dengan itu sebagai prinsip -. NET tampaknya baik-baik saja memerlukan jenis kunci yang tepat, misalnya - tetapi layak mengikuti alasan di posting blog. (Setelah disebutkan. NET, ada baiknya menjelaskan bagian dari alasan mengapa itu bukan masalah di. NET adalah bahwa ada masalah yang lebih besar di. NET varians yang lebih terbatas ...)
sumber
Integer
dan aDouble
tidak pernah bisa setara satu sama lain, masih merupakan pertanyaan yang wajar untuk bertanya apakah aSet<? extends Number>
mengandung nilainew Integer(5)
.Set<? extends Foo>
. Saya sangat sering mengubah jenis kunci peta dan kemudian merasa frustrasi karena kompilator tidak dapat menemukan semua tempat di mana kode perlu diperbarui. Saya benar-benar tidak yakin bahwa ini adalah tradeoff yang benar.Kontrak dinyatakan sebagai berikut:
(penekanan saya)
dan karena itu, pencarian kunci yang sukses tergantung pada implementasi kunci metode input kesetaraan. Itu tidak selalu tergantung pada kelas k.
sumber
hashCode()
. Tanpa implementasi hashCode () yang tepat, implementasi yang baikequals()
agak tidak berguna dalam kasus ini.get()
tidak perlu mengambil argumen tipeObject
untuk memuaskan kontak. Bayangkan metode get terbatas pada tipe kunciK
- kontrak masih akan valid. Tentu saja, penggunaan di mana jenis waktu kompilasi bukan subkelasK
sekarang akan gagal dikompilasi, tetapi itu tidak membatalkan kontrak, karena kontrak secara implisit mendiskusikan apa yang terjadi jika kode dikompilasi.Ini adalah penerapan Hukum Postel, "menjadi konservatif dalam apa yang Anda lakukan, menjadi liberal dalam apa yang Anda terima dari orang lain."
Pemeriksaan kesetaraan dapat dilakukan terlepas dari jenisnya; yang
equals
metode didefinisikan padaObject
kelas dan menerima setiapObject
sebagai parameter. Jadi, masuk akal untuk kesetaraan kunci, dan operasi berdasarkan kesetaraan kunci, untuk menerimaObject
jenis apa pun .Ketika sebuah peta mengembalikan nilai kunci, ia menyimpan sebanyak mungkin informasi jenis, dengan menggunakan parameter tipe.
sumber
V Get(K k)
di C #?V Get(K k)
di C # karena itu juga masuk akal. Perbedaan antara pendekatan Java dan .NET hanya benar-benar yang memblokir hal-hal yang tidak cocok. Di C # itu kompiler, di Jawa itu koleksi. Saya marah tentang kelas pengumpulan .NET yang tidak konsisten sesekali, tetapiGet()
danRemove()
hanya menerima jenis yang cocok tentu saja mencegah Anda dari melewatkan nilai yang salah secara tidak sengaja.contains : K -> boolean
.Saya pikir bagian Tutorial Generik ini menjelaskan situasi (penekanan saya):
"Anda perlu memastikan bahwa API generik tidak terlalu membatasi; API harus terus mendukung kontrak asli API. Pertimbangkan lagi beberapa contoh dari java.util.Collection. API pra-generik terlihat seperti:
Upaya naif untuk menghasilkan itu adalah:
Meskipun ini jenis aman, itu tidak sesuai dengan kontrak asli API. Metode containAll () bekerja dengan segala jenis koleksi yang masuk. Ini hanya akan berhasil jika koleksi yang masuk benar-benar hanya berisi contoh E, tetapi:
sumber
containsAll( Collection< ? extends E > c )
?containsAll
denganCollection<S>
manaS
adalah supertype dariE
. Ini tidak akan diizinkan jika itucontainsAll( Collection< ? extends E > c )
. Lebih jauh, seperti yang secara eksplisit dinyatakan dalam contoh, sah untuk meneruskan koleksi dari tipe yang berbeda (dengan nilai pengembalian kemudian menjadifalse
).Alasannya adalah bahwa penahanan ditentukan oleh
equals
danhashCode
yang merupakan metodeObject
dan keduanya mengambilObject
parameter. Ini adalah cacat desain awal di perpustakaan standar Jawa. Ditambah dengan keterbatasan dalam sistem tipe Java, itu memaksa apa pun yang bergantung pada equals dan kode hash untuk mengambilObject
.Satu-satunya cara untuk memiliki tabel hash tipe-aman dan kesetaraan di Jawa adalah untuk menghindari
Object.equals
danObject.hashCode
dan menggunakan pengganti generik. Java fungsional dilengkapi dengan kelas tipe hanya untuk tujuan ini:Hash<A>
danEqual<A>
. PembungkusHashMap<K, V>
disediakan yang mengambilHash<K>
danEqual<K>
dalam konstruktornya. Kelasget
dancontains
metode ini karenanya mengambil argumen tipe generikK
.Contoh:
sumber
Kesesuaian.
Sebelum obat generik tersedia, baru saja dapatkan (Objek o).
Jika mereka mengubah metode ini untuk mendapatkan (<K> o) itu akan berpotensi memaksa pemeliharaan kode besar ke pengguna java hanya untuk membuat kompilasi kode kerja lagi.
Mereka bisa memperkenalkan tambahan metode , katakanlah get_checked (<K> o) dan hentikan metode get () lama sehingga ada jalur transisi yang lebih lembut. Tetapi untuk beberapa alasan, ini tidak dilakukan. (Situasi yang kita hadapi sekarang adalah Anda perlu menginstal alat seperti findBugs untuk memeriksa kompatibilitas tipe antara argumen get () dan tipe kunci yang dideklarasikan <K> dari peta.)
Argumen yang berkaitan dengan semantik. Equals () adalah palsu, saya pikir. (Secara teknis mereka benar, tapi saya masih berpikir mereka palsu. Tidak ada desainer yang warasnya akan membuat o1.equals (o2) benar jika o1 dan o2 tidak memiliki superclass umum.)
sumber
Ada satu alasan lagi yang berat, itu tidak bisa dilakukan secara teknis, karena itu brokes Map.
Java memiliki konstruksi generik seperti polimorfik
<? extends SomeClass>
. Ditandai referensi tersebut dapat menunjuk ke jenis yang ditandatangani<AnySubclassOfSomeClass>
. Tetapi generik polimorfik membuat referensi itu hanya bisa dibaca . Kompiler memungkinkan Anda untuk menggunakan tipe generik hanya sebagai tipe metode pengembalian (seperti getter sederhana), tetapi memblokir metode yang tipe generiknya argumen (seperti setter biasa). Itu berarti jika Anda menulisMap<? extends KeyType, ValueType>
, kompiler tidak memungkinkan Anda memanggil metodeget(<? extends KeyType>)
, dan peta tidak akan berguna. Satu-satunya solusi adalah untuk membuat metode ini tidak generik:get(Object)
.sumber
Kompatibilitas mundur, saya kira.
Map
(atauHashMap
) masih perlu dukunganget(Object)
.sumber
put
(yang memang membatasi jenis generik). Anda mendapatkan kompatibilitas mundur dengan menggunakan tipe mentah. Generik adalah "opt-in".Saya melihat ini dan berpikir mengapa mereka melakukannya dengan cara ini. Saya tidak berpikir salah satu jawaban yang ada menjelaskan mengapa mereka tidak bisa membuat antarmuka generik baru hanya menerima jenis yang tepat untuk kunci. Alasan sebenarnya adalah bahwa meskipun mereka memperkenalkan obat generik, mereka TIDAK membuat antarmuka baru. Antarmuka Peta adalah Peta non-generik lama yang sama yang hanya berfungsi sebagai versi generik dan non-generik. Dengan cara ini jika Anda memiliki metode yang menerima Peta non-generik, Anda dapat meneruskannya
Map<String, Customer>
dan itu akan tetap berfungsi. Pada saat yang sama kontrak untuk menerima Obyek menerima sehingga antarmuka baru harus mendukung kontrak ini juga.Menurut pendapat saya mereka harus menambahkan antarmuka baru dan mengimplementasikan keduanya pada koleksi yang ada tetapi mereka memutuskan mendukung antarmuka yang kompatibel bahkan jika itu berarti desain yang lebih buruk untuk metode get. Perhatikan bahwa koleksi itu sendiri akan kompatibel dengan metode yang ada hanya antarmuka tidak akan.
sumber
Kami sedang melakukan refactoring besar sekarang dan kami kehilangan get () yang sangat diketik ini untuk memastikan bahwa kami tidak melewatkan get () dengan tipe lama.
Tapi saya menemukan trik penyelesaian / jelek untuk memeriksa waktu kompilasi: membuat antarmuka Peta dengan get sangat diketik, berisiKey, menghapus ... dan memasukkannya ke paket java.util proyek Anda.
Anda akan mendapatkan kesalahan kompilasi hanya untuk memanggil get (), ... dengan tipe yang salah, semua yang lain tampaknya ok untuk kompiler (setidaknya di dalam gerhana kepler).
Jangan lupa untuk menghapus antarmuka ini setelah memeriksa bangunan Anda karena ini bukan yang Anda inginkan di runtime.
sumber