Mengapa Java Map tidak menambah Koleksi?

146

Saya terkejut dengan fakta bahwa Map<?,?>itu bukan Collection<?>.

Saya pikir itu akan sangat masuk akal jika dinyatakan seperti itu:

public interface Map<K,V> extends Collection<Map.Entry<K,V>>

Lagi pula, a Map<K,V>adalah koleksi Map.Entry<K,V>, bukan?

Jadi apakah ada alasan bagus mengapa itu tidak diterapkan seperti itu?


Berkat Cletus untuk jawaban yang paling otoritatif, tapi aku masih bertanya-tanya mengapa, jika Anda sudah bisa melihat Map<K,V>seperti Set<Map.Entries<K,V>>(melalui entrySet()), tidak hanya memperpanjang antarmuka yang sebaliknya.

Jika a Mapadalah a Collection, apa saja elemennya? Satu-satunya jawaban yang masuk akal adalah "Pasangan nilai kunci"

Persis, interface Map<K,V> extends Set<Map.Entry<K,V>>pasti hebat!

tetapi ini memberikan Mapabstraksi yang sangat terbatas (dan tidak terlalu berguna) .

Tetapi jika itu yang terjadi maka mengapa entrySetditentukan oleh antarmuka? Entah bagaimana itu harus berguna (dan saya pikir mudah untuk memperdebatkan posisi itu!).

Anda tidak bisa menanyakan nilai apa yang diberikan peta kunci, Anda juga tidak bisa menghapus entri untuk kunci yang diberikan tanpa mengetahui nilai apa yang dipetakannya.

Saya tidak mengatakan bahwa hanya itu yang ada untuk itu Map! Itu bisa dan harus menyimpan semua metode lain (kecuali entrySet, yang berlebihan sekarang)!

polygenelubricants
sumber
1
event Set <Map.Entry <K, V >> sebenarnya
Maurice Perry
3
Hanya saja tidak. Ini didasarkan pada pendapat (seperti yang dijelaskan dalam FAQ Desain), daripada menjadi kesimpulan yang logis. Untuk pendekatan alternatif, lihat desain wadah di C ++ STL ( sgi.com/tech/stl/table_of_contents.html ) yang didasarkan pada analisis Stepanov yang menyeluruh dan brilian.
beldaz

Jawaban:

123

Dari FAQ Desain Java Collections API :

Mengapa Map tidak menambah Koleksi?

Ini dengan desain. Kami merasa bahwa pemetaan bukan koleksi dan koleksi bukan pemetaan. Dengan demikian, tidak masuk akal bagi Map untuk memperluas antarmuka Collection (atau sebaliknya).

Jika Peta adalah Koleksi, apa saja elemennya? Satu-satunya jawaban yang masuk akal adalah "Pasangan nilai-kunci", tetapi ini memberikan abstraksi Peta yang sangat terbatas (dan tidak terlalu berguna). Anda tidak bisa menanyakan nilai apa yang diberikan peta kunci, Anda juga tidak bisa menghapus entri untuk kunci yang diberikan tanpa mengetahui nilai apa yang dipetakannya.

Koleksi dapat dibuat untuk memperluas Peta, tetapi ini menimbulkan pertanyaan: apa kuncinya? Tidak ada jawaban yang benar-benar memuaskan, dan memaksa seseorang mengarah ke antarmuka yang tidak wajar.

Peta dapat dilihat sebagai Koleksi (dari kunci, nilai, atau pasangan), dan fakta ini tercermin dalam tiga "operasi tampilan Koleksi" pada Maps (keySet, entrySet, dan nilai). Walaupun pada prinsipnya memungkinkan untuk melihat Daftar sebagai peta pemetaan yang mengindeks elemen, ini memiliki properti jahat yang menghapus elemen dari Daftar mengubah Kunci yang dikaitkan dengan setiap elemen sebelum elemen yang dihapus. Itu sebabnya kami tidak memiliki operasi tampilan peta di Daftar.

Pembaruan: Saya pikir kutipan menjawab sebagian besar pertanyaan. Ada baiknya menekankan bagian tentang koleksi entri yang tidak menjadi abstraksi yang sangat berguna. Sebagai contoh:

Set<Map.Entry<String,String>>

akan memperbolehkan:

set.add(entry("hello", "world"));
set.add(entry("hello", "world 2");

(dengan asumsi entry()metode yang membuat Map.Entryinstance)

Maps memerlukan kunci unik sehingga ini akan melanggar ini. Atau jika Anda memaksakan kunci unik pada Setentri, itu tidak benar-benar Setdalam arti umum. Ini Setdengan pembatasan lebih lanjut.

Bisa dibilang Anda dapat mengatakan equals()/ hashCode()hubungan untuk Map.Entryitu murni pada kunci tetapi bahkan yang memiliki masalah. Lebih penting lagi, apakah itu benar-benar menambah nilai? Anda mungkin menemukan abstraksi ini rusak setelah Anda mulai melihat kasing sudut.

Perlu dicatat bahwa HashSetsebenarnya diimplementasikan sebagai HashMap, bukan sebaliknya. Ini murni detail implementasi tetapi tetap menarik.

Alasan utama untuk entrySet()ada adalah untuk menyederhanakan traversal sehingga Anda tidak perlu melintasi kunci dan kemudian melakukan pencarian kunci. Jangan menganggapnya sebagai bukti prima facie bahwa a Mapharus berupa Setentri (imho).

cletus
sumber
1
Pembaruan sangat meyakinkan, tetapi memang, tampilan Set dikembalikan oleh entrySet()tidak mendukung remove, sementara itu tidak mendukung add(mungkin melempar UnsupportedException). Jadi saya mengerti maksud Anda, tapi sekali lagi, saya melihat poin OP juga .. (Pendapat saya sendiri seperti yang dinyatakan dalam jawaban saya sendiri ..)
Enno Shioji
1
Zwei memunculkan poin yang bagus. Semua komplikasi yang disebabkan adddapat ditangani seperti halnya entrySet()tampilan: biarkan beberapa operasi dan jangan biarkan yang lain. Respons alami, tentu saja, adalah "Apa yang Settidak mendukung add?" - yah, jika perilaku seperti itu dapat diterima entrySet(), lalu mengapa itu tidak dapat diterima this? Yang mengatakan, saya saya sebagian besar yakin sudah mengapa hal ini bukan ide yang bagus karena saya pernah berpikir, tapi saya masih berpikir itu layak perdebatan lebih lanjut, jika hanya untuk memperkaya pemahaman saya sendiri dari apa yang membuat desain yang baik API / OOP.
polygenelubricants
3
Paragraf pertama mengutip dari FAQ itu lucu: "Kami merasa ... dengan demikian ... tidak masuk akal". Saya tidak merasa bahwa "merasa" dan "merasakan" merupakan kesimpulan; o)
Peter Wippermann
Koleksi dapat dibuat untuk memperpanjang Peta ? Tidak yakin, mengapa penulis berpikir untuk Collectionmemperpanjang Map?
pertukaran berlebihan
11

Meskipun Anda sudah mendapatkan sejumlah jawaban yang menjawab pertanyaan Anda secara langsung, saya pikir mungkin berguna untuk mundur sedikit, dan melihat pertanyaan itu sedikit lebih umum. Yaitu, tidak untuk melihat secara khusus bagaimana perpustakaan Java terjadi untuk ditulis, dan melihat mengapa itu ditulis seperti itu.

Masalahnya di sini adalah bahwa pewarisan hanya memodelkan satu jenis kesamaan. Jika Anda memilih dua hal yang keduanya tampak "seperti koleksi", Anda mungkin dapat memilih 8 atau 10 kesamaan yang mereka miliki. Jika Anda memilih sepasang yang berbeda dari "koleksi-seperti" hal-hal, mereka akan juga 8 atau 10 kesamaan - tetapi mereka tidak akan menjadi sama 8 atau 10 hal sebagai pasangan pertama.

Jika Anda melihat selusin berbeda "koleksi-seperti" hal, hampir setiap satu dari mereka mungkin akan memiliki sesuatu seperti 8 atau 10 karakteristik yang sama dengan setidaknya satu satu lain - tetapi jika Anda melihat apa yang dibagikan di seluruh setiap satu dari mereka, Anda tidak punya apa-apa.

Ini adalah situasi di mana warisan (terutama warisan tunggal) tidak dapat dimodelkan dengan baik. Tidak ada garis pemisah yang bersih di antara yang benar-benar koleksi dan yang tidak - tetapi jika Anda ingin mendefinisikan kelas Koleksi yang bermakna, Anda terjebak dengan mengabaikan beberapa di antaranya. Jika Anda hanya meninggalkan beberapa di antaranya, kelas Collection Anda hanya akan dapat memberikan antarmuka yang jarang. Jika Anda meninggalkan lebih banyak, Anda akan dapat memberikan antarmuka yang lebih kaya.

Beberapa juga mengambil opsi untuk mengatakan: "jenis koleksi ini mendukung operasi X, tetapi Anda tidak diizinkan menggunakannya, dengan berasal dari kelas dasar yang mendefinisikan X, tetapi mencoba menggunakan kelas turunan 'X gagal (mis. , dengan melemparkan pengecualian).

Itu masih menyisakan satu masalah: hampir terlepas dari apa yang Anda tinggalkan dan yang Anda masukkan, Anda harus menarik garis keras antara apa kelas yang masuk dan apa yang keluar. Tidak peduli di mana Anda menggambar garis itu, Anda akan dibiarkan dengan pembagian yang jelas, agak artifisial, antara beberapa hal yang sangat mirip.

Jerry Coffin
sumber
10

Saya kira mengapa itu subjektif.

Di C #, saya pikir Dictionarymemperluas atau setidaknya mengimplementasikan koleksi:

public class Dictionary<TKey, TValue> : IDictionary<TKey, TValue>, 
    ICollection<KeyValuePair<TKey, TValue>>, IEnumerable<KeyValuePair<TKey, TValue>>, 
    IDictionary, ICollection, IEnumerable, ISerializable, IDeserializationCallback

Di Pharo Smalltak juga:

Collection subclass: #Set
Set subclass: #Dictionary

Tetapi ada asimetri dengan beberapa metode. Misalnya, collect:akan mengambil asosiasi (setara dengan entri), sambil do:mengambil nilai. Mereka menyediakan metode lain keysAndValuesDo:untuk mengulang kamus dengan entri. Add:mengambil asosiasi, tetapi remove:telah "ditekan":

remove: anObject
self shouldNotImplement 

Jadi itu bisa dilakukan secara definitif, tetapi mengarah ke beberapa masalah lain mengenai hirarki kelas.

Apa yang lebih baik itu subjektif.

ewernli
sumber
3

Jawaban cletus bagus, tapi saya ingin menambahkan pendekatan semantik. Untuk menggabungkan keduanya tidak masuk akal, pikirkan kasus Anda menambahkan pasangan kunci-nilai melalui antarmuka koleksi dan kunci sudah ada. Map-interface memungkinkan hanya satu nilai yang terkait dengan kunci. Tetapi jika Anda secara otomatis menghapus entri yang ada dengan kunci yang sama, koleksi memiliki setelah menambahkan ukuran yang sama seperti sebelumnya - sangat tak terduga untuk koleksi.

Mnementh
sumber
4
Beginilah cara Setkerjanya, dan Set apakah diimplementasikan Collection.
Lii
2

Koleksi Java rusak. Ada antarmuka yang hilang, yaitu Relation. Karenanya, Map extends Relation extends Set. Hubungan (juga disebut multi-peta) memiliki pasangan nama-nilai yang unik. Peta (alias "Fungsi"), memiliki nama unik (atau kunci) yang tentu saja memetakan ke nilai. Urutan memperpanjang Peta (di mana setiap tombol bilangan bulat> 0). Tas (atau multi-set) memperluas Peta (di mana setiap tombol adalah elemen dan nilai masing-masing adalah berapa kali elemen muncul dalam tas).

Struktur ini akan memungkinkan persimpangan, gabungan dll dari berbagai "koleksi". Karenanya, hierarki harus:

                                Set

                                 |

                              Relation

                                 |

                                Map

                                / \

                             Bag Sequence

Sun / Oracle / Java ppl - lakukan dengan benar lain kali. Terima kasih.

xagyg
sumber
4
Saya ingin melihat detail API untuk antarmuka imajiner ini. Tidak yakin saya suka Sequence (alias Daftar) di mana kuncinya adalah Integer; itu terdengar seperti hit kinerja besar.
Lawrence Dol
Itu benar, urutan adalah daftar - karenanya urutan memperluas daftar. Saya tidak tahu apakah Anda dapat mengakses ini, tetapi ini mungkin portal.acm.org/citation.cfm?id=1838687.1838705 yang
xagyg
@SoftwareMonkey di sini adalah tautan lain. Tautan terakhir adalah tautan ke javadoc API. zedlib.sourceforge.net
xagyg
@SoftwareMonkey Saya tanpa malu-malu mengakui bahwa desain adalah perhatian utama saya di sini. Saya berasumsi para guru di Oracle / Sun dapat mengoptimalkan sih itu.
xagyg
Saya cenderung tidak setuju. Bagaimana bisa ia berpendapat bahwa "Map is-a Set" jika Anda tidak selalu dapat menambahkan elemen non-anggota domain ke set Anda (seperti pada contoh dalam jawaban @cletus ').
einpoklum
1

Jika Anda melihat struktur data masing-masing, Anda dapat dengan mudah menebak alasannya Map bukan bagian dari itu Collection. Masing-masing Collectionmenyimpan nilai tunggal di mana sebagai pasangan Mapkunci menyimpan nilai. Jadi metode dalam Collectionantarmuka tidak kompatibel untuk Mapantarmuka. Misalnya yang Collectionkita miliki add(Object o). Apa yang akan menjadi implementasi di Map. Tidak masuk akal untuk memiliki metode seperti itu di Map. Sebaliknya, kami memiliki put(key,value)metode dalam Map.

Argumen yang sama berlaku untuk addAll(), remove()dan removeAll()metode. Jadi alasan utama adalah perbedaan cara data disimpan di Mapdan Collection. Juga jika Anda ingat Collectionantarmuka yang diimplementasikan Iterableantarmuka yaitu setiap antarmuka dengan .iterator()metode harus mengembalikan iterator yang harus memungkinkan kita untuk mengulangi nilai-nilai yang disimpan di Collection. Sekarang apa yang akan dikembalikan dengan metode seperti itu Map? Iterator kunci atau iterator nilai? Ini juga tidak masuk akal.

Ada beberapa cara di mana kita dapat beralih pada kunci dan nilai yang disimpan dalam a Mapdan itu adalah bagian dari Collectionkerangka kerja.

Mayur Ingle
sumber
There are ways in which we can iterate over keys and values stores in a Map and that is how it is a part of Collection framework.Bisakah Anda menunjukkan contoh kode untuk ini?
RamenChef
Itu dijelaskan dengan sangat baik. Aku mengerti sekarang. Terima kasih!
Emir Memic
Implementasi add(Object o)akan menambahkan Entry<ClassA, ClassB>objek. A Mapdapat dianggap sebagaiCollection of Tuples
LIvanov
0

Persis, interface Map<K,V> extends Set<Map.Entry<K,V>>pasti hebat!

Sebenarnya, jika iya implements Map<K,V>, Set<Map.Entry<K,V>>, maka saya cenderung setuju .. Sepertinya wajar saja. Tapi itu tidak bekerja dengan baik, bukan? Katakanlah kita punya HashMap implements Map<K,V>, Set<Map.Entry<K,V>, LinkedHashMap implements Map<K,V>, Set<Map.Entry<K,V>dll ... itu semua baik, tetapi jika Anda punyaentrySet() , tidak ada yang akan lupa untuk menerapkan metode itu, dan Anda dapat yakin bahwa Anda bisa mendapatkan entriSetiap Peta apa pun, sedangkan Anda tidak melakukannya jika Anda berharap bahwa implementor telah mengimplementasikan kedua antarmuka ...

Alasan saya tidak ingin memiliki interface Map<K,V> extends Set<Map.Entry<K,V>>sederhana, karena akan ada lebih banyak metode. Lagi pula, mereka adalah hal yang berbeda, bukan? Juga sangat praktis, jika saya menekan map.IDE, saya tidak ingin melihat .remove(Object obj), dan .remove(Map.Entry<K,V> entry)karena saya tidak dapat melakukan hit ctrl+space, r, returndan menyelesaikannya.

Enno Shioji
sumber
Tampak bagi saya bahwa jika seseorang dapat mengklaim, secara semantik, bahwa "A Map is-a Set on Map.Entry's" maka orang akan repot menerapkan metode yang relevan; dan jika seseorang tidak dapat membuat pernyataan itu, maka seseorang tidak akan - yaitu itu harus tentang masalah.
einpoklum
0

Map<K,V>tidak boleh diperpanjang Set<Map.Entry<K,V>>sejak:

  • Anda tidak dapat menambahkan berbeda Map.Entrydengan kunci yang sama untuk yang sama Map, tetapi
  • Anda dapat menambahkan berbagai Map.Entrys dengan kunci yang sama ke yang sama Set<Map.Entry>.
einpoklum
sumber
... ini adalah pernyataan ringkas dari ghist dari bagian kedua dari jawaban @cletus.
einpoklum
-2

Lurus dan sederhana. Koleksi adalah antarmuka yang mengharapkan hanya satu Objek, sedangkan Peta membutuhkan Dua.

Collection(Object o);
Map<Object,Object>
Trinadh Koya
sumber
Nice Trinad, jawaban Anda selalu sederhana dan singkat.
Sairam