Mengapa tidak Set
menyediakan operasi untuk mendapatkan elemen yang sama dengan elemen lain?
Set<Foo> set = ...;
...
Foo foo = new Foo(1, 2, 3);
Foo bar = set.get(foo); // get the Foo element from the Set that equals foo
Saya bisa bertanya apakah Set
elemen itu mengandung elemen yang sama dengan bar
, jadi mengapa saya tidak bisa mendapatkan elemen itu? :(
Untuk memperjelas, equals
metode ini diganti, tetapi hanya memeriksa salah satu bidang, tidak semua. Jadi dua Foo
objek yang dianggap sama sebenarnya dapat memiliki nilai yang berbeda, itu sebabnya saya tidak bisa hanya menggunakan foo
.
java
collections
set
equals
foobar
sumber
sumber
SortedSet
dan implementasinya, yang berbasis peta (mis.TreeSet
Memungkinkan untuk mengaksesfirst()
).NSSet
) memiliki metode seperti itu. Disebutmember
dan mengembalikan objek dalam himpunan yang membandingkan "sama" dengan parametermember
metode (yang tentu saja bisa menjadi objek yang berbeda dan juga memiliki sifat yang berbeda, yang sama mungkin tidak memeriksa).Jawaban:
Tidak akan ada gunanya mendapatkan elemen jika itu sama. A
Map
lebih cocok untuk usecase ini.Jika Anda masih ingin menemukan elemen, Anda tidak memiliki pilihan lain selain menggunakan iterator:
sumber
Map
lebih cocok (Map<Foo, Foo>
dalam hal ini.)Map<Foo, Foo>
sebagai pengganti, downside adalah bahwa peta selalu harus menyimpan setidaknya kunci dan nilai (dan untuk kinerja itu juga harus menyimpan hash), sementara satu set bisa lolos hanya menyimpan nilai (dan mungkin hash untuk kinerja). Jadi implementasi perangkat yang baik bisa sama cepatnyaMap<Foo, Foo>
tetapi menggunakan memori hingga 50% lebih sedikit. Dalam kasus Java tidak masalah, karena HashSet secara internal didasarkan pada HashMap.Untuk menjawab pertanyaan yang tepat, " Mengapa tidak
Set
menyediakan operasi untuk mendapatkan elemen yang sama dengan elemen lain?", Jawabannya adalah: karena perancang kerangka koleksi tidak terlalu melihat ke depan. Mereka tidak mengantisipasi use case Anda yang sangat sah, dengan naif mencoba "memodelkan abstraksi set matematis" (dari javadoc) dan hanya lupa menambahkan yang bergunaget()
metode yang .Sekarang untuk pertanyaan tersirat " bagaimana Anda mendapatkan elemen itu": Saya pikir solusi terbaik adalah dengan menggunakan,
Map<E,E>
bukanSet<E>
, untuk memetakan elemen untuk diri mereka sendiri. Dengan cara itu, Anda dapat secara efisien mengambil elemen dari "set", karena metode get () dariMap
akan menemukan elemen menggunakan tabel hash yang efisien atau algoritma pohon. Jika Anda mau, Anda bisa menulis implementasi Anda sendiriSet
yang menawarkanget()
metode tambahan , merangkumMap
.Jawaban berikut menurut saya salah atau salah:
"Anda tidak perlu mendapatkan elemen, karena Anda sudah memiliki objek yang sama": pernyataan itu salah, seperti yang sudah Anda tunjukkan dalam pertanyaan. Dua objek yang sama masih dapat memiliki keadaan yang berbeda yang tidak relevan dengan objek kesetaraan. Tujuannya adalah untuk mendapatkan akses ke status elemen yang terkandung dalam
Set
, bukan status objek yang digunakan sebagai "kueri"."Anda tidak memiliki pilihan lain selain menggunakan iterator": itu adalah pencarian linier atas koleksi yang sama sekali tidak efisien untuk set besar (ironisnya, secara internal
Set
diatur sebagai peta hash atau pohon yang dapat ditanyakan dengan efisien). Jangan lakukan itu! Saya telah melihat masalah kinerja yang parah dalam sistem kehidupan nyata dengan menggunakan pendekatan itu. Menurut pendapat saya apa yang mengerikan tentangget()
metode yang hilang tidak begitu banyak sehingga agak sulit untuk mengatasinya, tetapi sebagian besar programmer akan menggunakan pendekatan pencarian linier tanpa memikirkan implikasinya.sumber
get()
. Dalam contoh Anda, saya akan sangat bingung dengan customerSet.get (thisCustomer). (Padahal, Peta, seperti yang disarankan oleh banyak jawaban) akan baik-baik saja dengan canonicalCustomerMap.get (pelanggan ini). Saya juga akan OK dengan metode yang lebih jelas namanya (seperti metode anggota Objective-C di NSSet).Jika Anda memiliki objek yang sama, mengapa Anda membutuhkannya dari set? Jika "sama" hanya dengan kunci, maka
Map
akan menjadi pilihan yang lebih baik.Bagaimanapun, berikut ini akan melakukannya:
Dengan Java 8 ini bisa menjadi satu liner:
sumber
Konversikan set ke daftar, lalu gunakan
get
metode daftarsumber
Set Default di Java, sayangnya, tidak dirancang untuk memberikan operasi "get", seperti yang dijelaskan jschreiner secara akurat.
Solusi menggunakan iterator untuk menemukan elemen yang menarik (disarankan oleh dacwe ) atau menghapus elemen dan menambahkannya kembali dengan nilainya diperbarui (disarankan oleh KyleM ), dapat bekerja, tetapi bisa sangat tidak efisien.
Meng-override implementasi equals sehingga objek non-equal adalah "equal", sebagaimana dinyatakan dengan benar oleh David Ogren , dapat dengan mudah menyebabkan masalah pemeliharaan.
Dan menggunakan Peta sebagai pengganti eksplisit (seperti yang disarankan oleh banyak orang), imho, membuat kode kurang elegan.
Jika tujuannya adalah untuk mendapatkan akses ke instance asli dari elemen yang terkandung dalam set (harap saya mengerti benar kasus penggunaan Anda), berikut adalah solusi lain yang mungkin.
Saya pribadi memiliki kebutuhan yang sama saat mengembangkan videogame client-server dengan Java. Dalam kasus saya, setiap klien memiliki salinan komponen yang disimpan di server dan masalahnya adalah kapan pun klien perlu memodifikasi objek server.
Melewati sebuah objek melalui internet berarti bahwa klien memiliki instance berbeda dari objek itu pula. Untuk mencocokkan contoh "disalin" ini dengan yang asli, saya memutuskan untuk menggunakan Java UUIDs.
Jadi saya membuat kelas abstrak UniqueItem, yang secara otomatis memberikan id unik acak untuk setiap instance dari subkelasnya.
UUID ini dibagi antara klien dan server contoh, jadi dengan cara ini bisa mudah untuk mencocokkan mereka hanya dengan menggunakan Peta.
Namun secara langsung menggunakan Peta di usecase yang sama masih tidak sempurna. Seseorang mungkin berpendapat bahwa menggunakan Peta mungkin lebih rumit untuk mempertahankan dan menangani.
Untuk alasan ini saya mengimplementasikan perpustakaan yang disebut MagicSet, yang membuat penggunaan Peta "transparan" kepada pengembang.
https://github.com/ricpacca/magicset
Seperti Java HashSet asli, MagicHashSet (yang merupakan salah satu implementasi MagicSet yang disediakan di perpustakaan) menggunakan backing HashMap, tetapi alih-alih memiliki elemen sebagai kunci dan nilai dummy sebagai nilai, ia menggunakan UUID elemen sebagai kunci dan elemen itu sendiri sebagai nilai. Ini tidak menyebabkan overhead dalam penggunaan memori dibandingkan dengan HashSet normal.
Selain itu, MagicSet dapat digunakan tepat sebagai Set, tetapi dengan beberapa metode lagi menyediakan fungsionalitas tambahan, seperti getFromId (), popFromId (), removeFromId (), dll.
Satu-satunya syarat untuk menggunakannya adalah elemen apa pun yang ingin Anda simpan di MagicSet perlu memperpanjang kelas abstrak UniqueItem.
Berikut adalah contoh kode, membayangkan untuk mengambil contoh asli dari sebuah kota dari MagicSet, memberikan contoh lain dari kota itu dengan UUID yang sama (atau bahkan hanya UUID-nya).
sumber
Jika set Anda sebenarnya a
NavigableSet<Foo>
(seperti aTreeSet
), danFoo implements Comparable<Foo>
, Anda dapat menggunakan(Terima kasih atas komentar @ eliran-malka untuk petunjuknya.)
sumber
Dengan Java 8 Anda dapat melakukan:
Tapi hati-hati, .get () melempar NoSuchElementException, atau Anda dapat memanipulasi item opsional.
sumber
item->item.equals(theItemYouAreLookingFor)
dapat disingkat menjaditheItemYouAreLookingFor::equals
Jika Anda hanya mendapatkan satu, ini tidak akan sangat berkinerja karena Anda akan mengulang semua elemen Anda tetapi ketika melakukan beberapa pengambilan pada set besar Anda akan melihat perbedaannya.
sumber
Mengapa:
Tampaknya Set memainkan peran yang berguna dalam menyediakan sarana perbandingan. Ini dirancang untuk tidak menyimpan elemen duplikat.
Karena niat / desain ini, jika seseorang mendapatkan () referensi ke objek yang disimpan, kemudian memutasinya, mungkin saja niat desain Set dapat digagalkan dan dapat menyebabkan perilaku yang tidak terduga.
Dari JavaDocs
Bagaimana:
Sekarang, ketika Stream telah diperkenalkan, seseorang dapat melakukan hal berikut
sumber
Bagaimana dengan menggunakan kelas Array?
output:
item satu, dua
sumber
Anda lebih baik menggunakan objek Java HashMap untuk tujuan itu http://download.oracle.com/javase/1,5.0/docs/api/java/util/HashMap.html
sumber
Saya tahu, ini telah ditanyakan dan dijawab lama, namun jika ada yang tertarik, inilah solusi saya - kelas set khusus yang didukung oleh HashMap:
http://pastebin.com/Qv6S91n9
Anda dapat dengan mudah menerapkan semua metode Set lainnya.
sumber
Telah ada yang melakukannya !! Jika Anda menggunakan Guava, cara cepat untuk mengubahnya menjadi peta adalah:
sumber
Anda dapat menggunakan kelas Iterator
sumber
Jika Anda ingin nth Element dari HashSet, Anda dapat pergi dengan solusi di bawah ini, di sini saya telah menambahkan objek ModelClass di HashSet.
sumber
Jika Anda melihat beberapa baris pertama implementasi
java.util.HashSet
Anda akan melihat:Jadi,
HashSet
gunakanHashMap
interally, yang berarti bahwa jika Anda hanya menggunakan secaraHashMap
langsung dan menggunakan nilai yang sama dengan kunci dan nilai Anda akan mendapatkan efek yang Anda inginkan dan menghemat sedikit memori.sumber
sepertinya objek yang tepat untuk digunakan adalah Interner dari jambu biji:
Ini juga memiliki beberapa tuas yang sangat menarik, seperti concurrencyLevel, atau jenis referensi yang digunakan (mungkin perlu dicatat bahwa itu tidak menawarkan SoftInterner yang saya lihat lebih berguna daripada WeakInterner).
sumber
Karena implementasi tertentu dari Set mungkin atau mungkin bukan akses acak .
Anda selalu bisa mendapatkan iterator dan melangkah melalui Set, menggunakan metode iterators
next()
untuk mengembalikan hasil yang Anda inginkan setelah Anda menemukan elemen yang sama. Ini berfungsi terlepas dari implementasinya. Jika implementasinya BUKAN akses acak (gambar Set yang didukung daftar-tertaut),get(E element)
metode di antarmuka akan menipu, karena harus mengulangi koleksi untuk menemukan elemen yang akan dikembalikan, danget(E element)
tampaknya akan menyiratkan ini akan menjadi perlu, bahwa Set bisa melompat langsung ke elemen untuk mendapatkan.contains()
mungkin atau mungkin tidak harus melakukan hal yang sama, tentu saja, tergantung pada implementasinya, tetapi nama tersebut sepertinya tidak cocok dengan kesalahpahaman yang sama.sumber
Ya, gunakan
HashMap
... tetapi dengan cara khusus: jebakan yang saya ramalkan dalam mencoba menggunakan aHashMap
sebagai pseudo-Set
adalah kemungkinan kebingungan antara elemen "aktual" elemenMap/Set
, dan "kandidat", yaitu elemen yang digunakan untuk menguji apakah suatuequal
elemen sudah ada. Ini jauh dari sangat mudah, tetapi mendorong Anda menjauh dari perangkap:Kemudian lakukan ini:
Tapi ... Anda sekarang ingin
candidate
merusak diri sendiri dalam beberapa cara kecuali programmer benar-benar segera meletakkannya diMap/Set
... Anda ingincontains
"mencemari"candidate
sehingga setiap penggunaannya kecuali jika bergabung denganMap
membuatnya menjadi "kutukan" ". Mungkin Anda bisa membuatSomeClass
implement yang baruTaintable
antarmuka .Solusi yang lebih memuaskan adalah GettableSet , seperti di bawah ini. Namun, agar ini berfungsi, Anda harus bertanggung jawab atas desain
SomeClass
untuk membuat semua konstruktor tidak terlihat (atau ... mampu dan mau merancang dan menggunakan kelas wrapper untuk itu):Penerapan:
NoVisibleConstructor
Kelas Anda kemudian terlihat seperti ini:PS satu masalah teknis dengan
NoVisibleConstructor
kelas seperti itu: mungkin keberatan bahwa kelas seperti itu secara inherenfinal
, yang mungkin tidak diinginkan. Sebenarnya Anda selalu bisa menambahkanprotected
konstruktor tanpa parameter dummy :... yang paling tidak akan membiarkan kompilasi subclass. Anda kemudian harus memikirkan apakah Anda perlu memasukkan
getOrCreate()
metode pabrik lain dalam subkelas.Langkah terakhir adalah kelas dasar abstrak (NB "elemen" untuk daftar, "anggota" untuk set) seperti ini untuk anggota set Anda (bila mungkin - lagi, ruang lingkup untuk menggunakan kelas wrapper di mana kelas tidak di bawah kendali Anda, atau sudah memiliki kelas dasar, dll.), untuk menyembunyikan implementasi maksimal:
... penggunaan cukup jelas (dalam Anda
SomeClass
'sstatic
metode pabrik):sumber
Kontrak kode hash memperjelas bahwa:
Jadi asumsi Anda:
salah dan Anda melanggar kontrak. Jika kita melihat metode "berisi" Set antarmuka, kita memiliki itu:
Untuk mencapai apa yang Anda inginkan, Anda bisa menggunakan Peta tempat Anda mendefinisikan kunci dan menyimpan elemen Anda dengan kunci yang mendefinisikan bagaimana benda-benda berbeda atau sama satu sama lain.
sumber
Metode penolong cepat yang dapat mengatasi situasi ini:
sumber
Mengikuti bisa menjadi pendekatan
sumber
Coba gunakan array:
sumber