Mengapa Collection.remove (Object o) tidak generik?
Sepertinya Collection<E>
bisa sajaboolean remove(E o);
Kemudian, ketika Anda secara tidak sengaja mencoba untuk menghapus (misalnya) Set<String>
alih-alih setiap String individu dari a Collection<String>
, itu akan menjadi kesalahan waktu kompilasi alih-alih masalah debugging nanti.
java
api
generics
collections
Chris Mazzola
sumber
sumber
Jawaban:
Josh Bloch dan Bill Pugh merujuk pada masalah ini di Java Puzzlers IV: The Phantom Reference Menace, Attack of the Clone, dan Revenge of The Shift .
Josh Bloch mengatakan (6:41) bahwa mereka berusaha membuat metode get get Map, menghapus metode dan lainnya, tetapi "itu tidak berhasil".
Ada terlalu banyak program masuk akal yang tidak dapat dibuat jika Anda hanya mengizinkan tipe umum koleksi sebagai tipe parameter. Contoh yang diberikan oleh dia adalah persimpangan a
List
dariNumber
danList
dariLong
s.sumber
remove()
(dalamMap
maupun dalamCollection
) tidak generik karena Anda harus dapat meneruskan semua jenis objekremove()
. Objek yang dihapus tidak harus bertipe sama dengan objek yang Anda lewatiremove()
; hanya mensyaratkan bahwa mereka setara. Dari spesifikasiremove()
,remove(o)
menghapus objeke
seperti itu(o==null ? e==null : o.equals(e))
adalahtrue
. Perhatikan bahwa tidak ada yang membutuhkano
dane
memiliki tipe yang sama. Ini mengikuti dari kenyataan bahwaequals()
metode mengambilObject
parameter sebagai, bukan hanya tipe yang sama dengan objek.Meskipun, mungkin secara umum benar bahwa banyak kelas telah
equals()
didefinisikan sehingga objeknya hanya bisa sama dengan objek kelasnya sendiri, yang tentu saja tidak selalu demikian. Misalnya, spesifikasi untukList.equals()
mengatakan bahwa dua objek Daftar sama jika keduanya Daftar dan memiliki konten yang sama, bahkan jika mereka berbeda implementasiList
. Jadi kembali ke contoh dalam pertanyaan ini, adalah mungkin untuk memilikiMap<ArrayList, Something>
dan bagi saya untuk memanggilremove()
denganLinkedList
argumen sebagai, dan itu harus menghapus kunci yang merupakan daftar dengan konten yang sama. Ini tidak akan mungkin terjadi jikaremove()
bersifat generik dan membatasi jenis argumennya.sumber
equals()
metode juga? Saya bisa melihat lebih banyak manfaat untuk mengetik keamanan daripada pendekatan "libertarian" ini. Saya pikir sebagian besar kasus dengan implementasi saat ini adalah untuk bug yang masuk ke kode kita daripada tentang keceriaan fleksibilitas yangremove()
dibawa metode ini.equals()
metode"?T
harus dinyatakan sebagai parameter tipe di kelas, danObject
tidak memiliki parameter tipe apa pun. Tidak ada cara untuk memiliki tipe yang mengacu pada "kelas yang mendeklarasikan".Equality<T>
dengannyaequals(T other)
. Maka Anda bisaremove(Equality<T> o)
dano
hanya beberapa objek yang dapat dibandingkan dengan yang lainT
.Karena jika parameter tipe Anda adalah wildcard, Anda tidak dapat menggunakan metode penghapusan generik.
Saya ingat untuk kembali ke pertanyaan ini dengan metode get (Object) Map. Metode get dalam kasus ini tidak generik, meskipun seharusnya diharapkan akan melewati objek dengan tipe yang sama dengan parameter tipe pertama. Saya menyadari bahwa jika Anda mengitari Maps dengan wildcard sebagai parameter tipe pertama, maka tidak ada cara untuk mengeluarkan elemen dari Peta dengan metode itu, jika argumen itu generik. Argumen wildcard tidak dapat benar-benar dipenuhi, karena kompiler tidak dapat menjamin bahwa jenisnya benar. Saya berspekulasi bahwa alasan add bersifat generik adalah karena Anda diharapkan untuk memastikan bahwa jenisnya benar sebelum menambahkannya ke koleksi. Namun, saat menghapus objek, jika jenisnya salah maka tidak akan cocok dengan apa pun.
Saya mungkin tidak menjelaskannya dengan sangat baik, tetapi tampaknya cukup logis bagi saya.
sumber
Selain jawaban lain, ada alasan lain mengapa metode ini harus menerima
Object
, yang merupakan predikat. Pertimbangkan contoh berikut:Intinya adalah bahwa objek yang diteruskan ke
remove
metode bertanggung jawab untuk mendefinisikanequals
metode. Membangun predikat menjadi sangat sederhana dengan cara ini.sumber
yourObject.equals(developer)
, sebagaimana didokumentasikan dalam Collections API: java.sun.com/javase/6/docs/api/java/util/…equals
metode ini, yaitu simetri. Metode remove hanya terikat pada spesifikasinya selama objek Anda memenuhi spec equals / hashCode, jadi implementasi apa pun akan bebas melakukan perbandingan dengan cara sebaliknya. Selain itu, objek predikat Anda tidak menerapkan.hashCode()
metode (tidak dapat menerapkan secara konsisten dengan yang sama), sehingga panggilan hapus tidak akan berfungsi pada koleksi berbasis Hash (seperti HashSet atau HashMap.keys ()). Bahwa ia bekerja dengan ArrayList adalah keberuntungan murni.Collection.remove
, tanpa melanggar kontraknya (jika pemesanan konsisten dengan yang sama). Dan beragam ArrayList (atau AbstractCollection, saya pikir) dengan panggilan yang sama berbalik masih akan menerapkan kontrak dengan benar - itu adalah kesalahan Anda jika itu tidak berfungsi sebagaimana dimaksud, karena Anda melanggarequals
kontrak.Asumsikan satu memiliki koleksi
Cat
, dan beberapa referensi objek jenisAnimal
,Cat
,SiameseCat
, danDog
. Menanyakan koleksi apakah berisi objek yang dirujuk olehCat
atauSiameseCat
referensi tampaknya masuk akal. Bertanya apakah itu berisi objek yang dirujuk olehAnimal
referensi mungkin tampak cerdik, tapi itu masih masuk akal. Objek yang dimaksud mungkin, setelah semua, menjadi aCat
, dan mungkin muncul dalam koleksi.Lebih jauh, bahkan jika objek kebetulan bukan sesuatu yang lain
Cat
, tidak ada masalah untuk mengatakan apakah itu muncul dalam koleksi - cukup jawab "tidak, tidak". Koleksi "lookup-style" dari beberapa jenis harus dapat menerima referensi supertipe apa pun secara bermakna dan menentukan apakah objek tersebut ada di dalam koleksi. Jika referensi objek yang diteruskan adalah tipe yang tidak terkait, tidak mungkin koleksi tersebut dapat memuatnya, jadi kueri dalam beberapa hal tidak bermakna (itu akan selalu menjawab "tidak"). Meskipun demikian, karena tidak ada cara untuk membatasi parameter menjadi subtipe atau supertipe, itu paling praktis untuk hanya menerima jenis apa pun dan menjawab "tidak" untuk objek apa pun yang jenisnya tidak terkait dengan koleksi.sumber
Comparable
parameterisasi untuk jenis yang dapat Anda bandingkan). Maka tidak masuk akal untuk membiarkan orang melewati sesuatu dari jenis yang tidak terkait.A
danB
satu jenis, danX
dan yangY
lainnya, sedemikian rupa sehinggaA
>B
, danX
>Y
. BaikA
>Y
danY
<A
, atauX
>B
danB
<X
. Hubungan-hubungan itu hanya dapat ada jika perbandingan besarnya mengetahui tentang kedua tipe tersebut. Sebaliknya, metode perbandingan kesetaraan objek dapat dengan mudah menyatakan dirinya tidak setara dengan apa pun dari jenis lainnya, tanpa harus mengetahui apa pun tentang jenis lainnya yang dimaksud. Objek bertipeCat
mungkin tidak tahu apakah itu ...FordMustang
, tetapi seharusnya tidak mengalami kesulitan untuk mengatakan apakah itu sama dengan objek seperti itu (jawabannya, jelas, menjadi "tidak").Saya selalu mengira ini karena remove () tidak punya alasan untuk peduli jenis objek yang Anda berikan. Cukup mudah, apa pun, untuk memeriksa apakah objek itu adalah salah satu yang terdapat dalam Koleksi, karena ia dapat memanggil equals () pada apa pun. Penting untuk memeriksa tipe pada add () untuk memastikan bahwa itu hanya berisi objek dari tipe itu.
sumber
Itu kompromi. Kedua pendekatan memiliki keunggulan:
remove(Object o)
remove(E e)
membawa lebih banyak jenis keamanan untuk apa yang sebagian besar program ingin lakukan dengan mendeteksi bug halus pada waktu kompilasi, seperti keliru mencoba menghapus integer dari daftar celana pendek.Kompatibilitas mundur selalu menjadi tujuan utama ketika mengembangkan Java API, oleh karena itu hapus (Objek o) dipilih karena membuatnya membuat kode yang ada lebih mudah. Jika kompatibilitas mundur TIDAK menjadi masalah, saya kira para desainer akan memilih hapus (E e).
sumber
Hapus bukan metode generik sehingga kode yang ada menggunakan koleksi non-generik akan tetap dikompilasi dan masih memiliki perilaku yang sama.
Lihat http://www.ibm.com/developerworks/java/library/j-jtp01255.html untuk detailnya.
Sunting: Seorang komentator bertanya mengapa metode add bersifat generik. [... menghapus penjelasan saya ...] Komentator kedua menjawab pertanyaan dari firebird84 jauh lebih baik daripada saya.
sumber
Alasan lain adalah karena antarmuka. Berikut adalah contoh untuk menunjukkannya:
sumber
remove()
tidak kovarian. Namun, pertanyaannya adalah apakah ini harus diizinkan.ArrayList#remove()
bekerja dengan cara kesetaraan nilai, bukan referensi kesetaraan. Mengapa Anda berharap bahwa aB
akan sama dengan aA
? Dalam contoh Anda, itu bisa saja, tapi itu harapan yang aneh. Saya lebih suka melihat Anda memberikanMyClass
argumen di sini.Karena itu akan merusak kode yang ada (pra-Java5). misalnya,
Sekarang Anda mungkin mengatakan kode di atas salah, tetapi anggaplah bahwa o berasal dari sekumpulan objek yang heterogen (yaitu berisi string, angka, objek, dll.). Anda ingin menghapus semua pertandingan, yang sah karena menghapus hanya akan mengabaikan non-string karena mereka tidak setara. Tetapi jika Anda membuatnya menghapus (String o), itu tidak lagi berfungsi.
sumber