Bagaimana cara menyimpan kunci duplikat di peta di Jawa?

1. Ikhtisar

Dalam tutorial ini, kita akan menjelajahi opsi yang tersedia untuk menangani Peta dengan kunci duplikat atau, dengan kata lain, Peta yang memungkinkan penyimpanan beberapa nilai untuk satu kunci.

2. Peta Standar

Java memiliki beberapa implementasi Peta antarmuka , masing-masing dengan kekhususannya sendiri.

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

Seperti yang bisa kita lihat, jika kita mencoba memasukkan dua nilai untuk kunci yang sama, nilai kedua akan disimpan, sedangkan yang pertama akan dihapus.

Ini juga akan dikembalikan (dengan setiap implementasi yang tepat dari metode put (kunci K, nilai V) ):

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

Lalu bagaimana kita bisa mencapai perilaku yang diinginkan?

3. Koleksi sebagai Nilai

Jelas, menggunakan Collection untuk setiap nilai dari Map kita akan berhasil:

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"); 
    

Namun, solusi verbose ini memiliki banyak kekurangan dan rentan terhadap kesalahan. Ini menyiratkan bahwa kita perlu membuat instance Collection untuk setiap nilai, memeriksa keberadaannya sebelum menambahkan atau menghapus nilai, menghapusnya secara manual ketika tidak ada nilai yang tersisa, dan sebagainya.

Dari Java 8 kita dapat mengeksploitasi metode compute () 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"); 
    

Meskipun ini adalah sesuatu yang perlu diketahui, kita harus menghindarinya kecuali memiliki alasan yang sangat bagus untuk tidak melakukannya, seperti kebijakan perusahaan yang membatasi yang mencegah kita menggunakan perpustakaan pihak ketiga.

Jika tidak, sebelum menulis implementasi Peta khusus kita sendiri dan menemukan kembali roda, kita harus memilih di antara beberapa opsi yang tersedia di luar kotak.

4. Koleksi Apache Commons

Seperti biasa, Apache punya solusi untuk masalah kita.

Mari kita mulai dengan mengimpor rilis terbaru dari Common Collections (CC mulai sekarang):

 org.apache.commons commons-collections4 4.1 

4.1. MultiMap

File org.apache.commons.collections4. Antarmuka MultiMap mendefinisikan Peta yang menyimpan kumpulan nilai terhadap setiap kunci.

Ini diimplementasikan oleh org.apache.commons.collections4.map. Kelas MultiValueMap , yang secara otomatis menangani sebagian besar boilerplate di bawah tenda:

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

Meskipun kelas ini tersedia sejak CC 3.2, ini tidak aman untuk thread , dan sudah tidak digunakan lagi di CC 4.1 . Kami harus menggunakannya hanya jika kami tidak dapat meningkatkan ke versi yang lebih baru.

4.2. MultiValuedMap

Penerus MultiMap adalah org.apache.commons.collections4. Antarmuka MultiValuedMap . Ini memiliki beberapa implementasi yang siap digunakan.

Mari kita lihat bagaimana menyimpan beberapa nilai kita ke dalam ArrayList , yang menyimpan duplikat:

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"); 

Alternatively, we could use a HashSet, which drops duplicates:

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

Both of the above implementations are not thread-safe.

Let's see how we can use the UnmodifiableMultiValuedMap decorator to make them immutable:

@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. Guava Multimap

Guava is the Google Core Libraries for Java API.

The com.google.common.collect.Multimap interface is there since version 2. At the time of writing the latest release is the 25, but since after version 23 it's been split in different branches for jre and android (25.0-jre and 25.0-android), we'll still use version 23 for our examples.

Let's start by importing Guava on our project:

 com.google.guava guava 23.0 

Guava followed the path of the multiple implementations since the beginning.

The most common one is the com.google.common.collect.ArrayListMultimap, which uses a HashMap backed by an ArrayList for every value:

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

As always, we should prefer the immutable implementations of the Multimap interface: com.google.common.collect.ImmutableListMultimap and com.google.common.collect.ImmutableSetMultimap.

5.1. Common Map Implementations

When we need a specific Map implementation, the first thing to do is check if it exists, because probably Guava has already implemented it.

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 menjelajahi implementasi paling populer dari Apache Commons Collections dan Guava, yang sebaiknya dipilih daripada solusi khusus jika memungkinkan.

Seperti biasa, kode sumber lengkap tersedia di Github.