Bagaimana Menyimpan Kunci Pendua dalam Peta di Jawa?

1. Gambaran keseluruhan

Dalam tutorial ini, kita akan meneroka pilihan yang ada untuk menangani Peta dengan kunci pendua atau, dengan kata lain, Peta yang membolehkan menyimpan beberapa nilai untuk satu kunci.

2. Peta Piawai

Java mempunyai beberapa implementasi Peta antara muka , masing-masing dengan keistimewaannya sendiri.

Namun, tidak ada implementasi Peta inti Java yang ada yang memungkinkan Peta untuk menangani beberapa nilai untuk satu kunci .

Seperti yang kita lihat, jika kita cuba memasukkan dua nilai untuk kunci yang sama, nilai kedua akan disimpan, sementara yang pertama akan dijatuhkan.

Ia juga akan dikembalikan (dengan setiap pelaksanaan metode put (kunci K, nilai V) yang tepat ):

Map map = new HashMap(); assertThat(map.put("key1", "value1")).isEqualTo(null); assertThat(map.put("key1", "value2")).isEqualTo("value1"); assertThat(map.get("key1")).isEqualTo("value2"); 

Bagaimana kita dapat mencapai tingkah laku yang diinginkan?

3. Pengumpulan sebagai Nilai

Jelas sekali, menggunakan Koleksi untuk setiap nilai Peta kami akan berjaya:

Map
    
      map = new HashMap(); List list = new ArrayList(); map.put("key1", list); map.get("key1").add("value1"); map.get("key1").add("value2"); assertThat(map.get("key1").get(0)).isEqualTo("value1"); assertThat(map.get("key1").get(1)).isEqualTo("value2"); 
    

Walau bagaimanapun, penyelesaian verbose ini mempunyai banyak kelemahan dan terdedah kepada kesalahan. Ini menyiratkan bahawa kita perlu membuat Koleksi untuk setiap nilai, periksa keberadaannya sebelum menambahkan atau membuang nilai, menghapusnya secara manual apabila tidak ada nilai yang tersisa, dan sebagainya.

Dari Java 8 kita dapat memanfaatkan kaedah komputasi () dan memperbaikinya:

Map
    
      map = new HashMap(); map.computeIfAbsent("key1", k -> new ArrayList()).add("value1"); map.computeIfAbsent("key1", k -> new ArrayList()).add("value2"); assertThat(map.get("key1").get(0)).isEqualTo("value1"); assertThat(map.get("key1").get(1)).isEqualTo("value2"); 
    

Walaupun ini adalah sesuatu yang perlu diketahui, kita harus menghindarinya kecuali mempunyai alasan yang sangat baik untuk tidak, seperti polisi syarikat yang menyekat yang menghalang kita daripada menggunakan perpustakaan pihak ketiga.

Jika tidak, sebelum menulis pelaksanaan Peta tersuai kita sendiri dan mencipta semula roda, kita harus memilih antara beberapa pilihan yang tersedia di luar kotak.

4. Koleksi Apache Commons

Seperti biasa, Apache ada penyelesaian untuk masalah kita.

Mari mulakan dengan mengimport keluaran terbaru Koleksi Umum (CC mulai sekarang):

 org.apache.commons commons-collections4 4.1 

4.1. Pelbagai Peta

The org.apache.commons.collections4. Antara muka MultiMap mentakrifkan Peta yang menyimpan sekumpulan nilai terhadap setiap kunci.

Ia dilaksanakan oleh org.apache.commons.collections4.map. Kelas MultiValueMap , yang secara automatik mengendalikan sebahagian besar plat boiler di bawah tudung:

MultiMap map = new MultiValueMap(); map.put("key1", "value1"); map.put("key1", "value2"); assertThat((Collection) map.get("key1")) .contains("value1", "value2"); 

Walaupun kelas ini tersedia sejak CC 3.2, kelas ini tidak selamat dari benang , dan sudah tidak digunakan lagi di CC 4.1 . Kita harus menggunakannya hanya apabila kita tidak dapat meningkatkan ke versi yang lebih baru.

4.2. Peta Pelbagai Nilai

Pengganti MultiMap adalah org.apache.commons.collections4. Antara muka MultiValuedMap . Ia mempunyai pelbagai implementasi yang siap digunakan.

Mari lihat bagaimana menyimpan pelbagai nilai kita ke dalam ArrayList , yang mengekalkan pendua:

MultiValuedMap map = new ArrayListValuedHashMap(); map.put("key1", "value1"); map.put("key1", "value2"); map.put("key1", "value2"); assertThat((Collection) map.get("key1")) .containsExactly("value1", "value2", "value2"); 

Sebagai alternatif, kita boleh menggunakan HashSet , yang menjatuhkan pendua:

MultiValuedMap map = new HashSetValuedHashMap(); map.put("key1", "value1"); map.put("key1", "value1"); assertThat((Collection) map.get("key1")) .containsExactly("value1"); 

Kedua-dua pelaksanaan di atas tidak selamat di dalam benang .

Mari lihat bagaimana kita boleh menggunakan penghias UnmodifiableMultiValuedMap untuk menjadikannya tidak berubah:

@Test(expected = UnsupportedOperationException.class) public void givenUnmodifiableMultiValuedMap_whenInserting_thenThrowingException() { MultiValuedMap map = new ArrayListValuedHashMap(); map.put("key1", "value1"); map.put("key1", "value2"); MultiValuedMap immutableMap = MultiMapUtils.unmodifiableMultiValuedMap(map); immutableMap.put("key1", "value3"); } 

5. Multimap Jambu Batu

Jambu batu adalah Perpustakaan Teras Google untuk API Java.

The com.google.common.collect. Multimap muka ada sejak versi 2. Pada masa yang menulis keluaran terbaru adalah 25, tetapi sejak selepas versi 23 ia telah berpecah di cawangan yang berbeza untuk jre dan android ( 25.0-jre dan 25.0-android ), kita akan masih digunakan versi 23 untuk contoh kami.

Mari mulakan dengan mengimport Jambu batu pada projek kami:

 com.google.guava guava 23.0 

Jambu telah mengikuti jalan dari banyak implementasi sejak awal.

Yang paling biasa adalah com.google.common.collect. ArrayListMultimap , yang menggunakan HashMap yang disokong oleh ArrayList untuk setiap nilai:

Multimap map = ArrayListMultimap.create(); map.put("key1", "value2"); map.put("key1", "value1"); assertThat((Collection) map.get("key1")) .containsExactly("value2", "value1"); 

Seperti biasa, kita harus memilih pelaksanaan yang tidak berubah dari antara muka Multimap: com.google.common.collect. ImmutableListMultimap dan com.google.common.collect. ImmutableSetMultimap .

5.1. Pelaksanaan Peta Biasa

Apabila kita memerlukan implementasi Peta tertentu , perkara pertama yang harus dilakukan adalah memeriksa apakah itu ada, kerana mungkin Jambu telah melaksanakannya.

For example, we can use the com.google.common.collect.LinkedHashMultimap, which preserves the insertion order of keys and values:

Multimap map = LinkedHashMultimap.create(); map.put("key1", "value3"); map.put("key1", "value1"); map.put("key1", "value2"); assertThat((Collection) map.get("key1")) .containsExactly("value3", "value1", "value2"); 

Alternatively, we can use a com.google.common.collect.TreeMultimap, which iterates keys and values in their natural order:

Multimap map = TreeMultimap.create(); map.put("key1", "value3"); map.put("key1", "value1"); map.put("key1", "value2"); assertThat((Collection) map.get("key1")) .containsExactly("value1", "value2", "value3"); 

5.2. Forging Our Custom MultiMap

Many other implementations are available.

However, we may want to decorate a Map and/or a List not yet implemented.

Luckily, Guava has a factory method allowing us to do it: the Multimap.newMultimap().

6. Conclusion

We've seen how to store multiple values for a key in a Map in all the main existing ways.

Kami telah meneroka implementasi Apache Commons Collections dan Jambu Batu yang paling popular, yang semestinya lebih disukai daripada penyelesaian khusus jika boleh.

Seperti biasa, kod sumber penuh tersedia di Github.