Hoe kan ik dubbele sleutels in een kaart in Java opslaan?

1. Overzicht

In deze zelfstudie gaan we de beschikbare opties onderzoeken voor het omgaan met een Kaart met dubbele sleutels of, met andere woorden, een Kaart waarmee u meerdere waarden voor een enkele sleutel kunt opslaan.

2. Standaardkaarten

Java heeft verschillende implementaties van de interface Kaart, elk met zijn eigen bijzonderheden.

Echter, geen van de bestaande Java Core Map-implementaties staat een Kaart om meerdere waarden voor een enkele sleutel af te handelen.

Zoals we kunnen zien, wordt de tweede waarde opgeslagen als we proberen twee waarden in te voegen voor dezelfde sleutel, terwijl de eerste wordt verwijderd.

Het zal ook worden geretourneerd (bij elke juiste implementatie van het put (K-toets, V-waarde) methode):

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

Hoe kunnen we dan het gewenste gedrag bereiken?

3. Verzameling als waarde

Het is duidelijk dat u een Verzameling voor elke waarde van onze Kaart zou het werk doen:

Kaart map = nieuwe HashMap (); Lijstlijst = nieuwe ArrayList (); map.put ("key1", lijst); 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"); 

Deze uitgebreide oplossing heeft echter meerdere nadelen en is vatbaar voor fouten. Het impliceert dat we een instantiëren Verzameling controleer voor elke waarde of deze aanwezig is voordat u een waarde toevoegt of verwijdert, verwijder deze handmatig als er geen waarden over zijn, enzovoort.

Vanaf Java 8 konden we het berekenen() methoden en verbeter het:

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

Hoewel dit de moeite waard is om te weten, moeten we het vermijden, tenzij we een zeer goede reden hebben om dat niet te doen, zoals een restrictief bedrijfsbeleid dat ons belet bibliotheken van derden te gebruiken.

Anders, voordat we onze eigen gewoonte schrijven Kaart implementatie en het wiel opnieuw uitvinden, moeten we kiezen uit de verschillende opties die out-of-the-box beschikbaar zijn.

4. Apache Commons-verzamelingen

Zoals gewoonlijk, Apache heeft een oplossing voor ons probleem.

Laten we beginnen met het importeren van de nieuwste release van Gemeenschappelijke collecties (CC vanaf nu):

 org.apache.commons commons-collections4 4.1 

4.1. MultiMap

De org.apache.commons.collections4.MultiMap interface definieert een kaart die een verzameling waarden voor elke sleutel bevat.

Het is geïmplementeerd door de org.apache.commons.collections4.map.MultiValueMap klasse, die automatisch het grootste deel van de boilerplate onder de motorkap behandelt:

MultiMap-kaart = nieuwe MultiValueMap (); map.put ("key1", "value1"); map.put ("key1", "value2"); assertThat ((Verzameling) map.get ("key1")) .contains ("waarde1", "waarde2"); 

Hoewel deze klasse beschikbaar is sinds CC 3.2, het is niet draadveilig, en het is verouderd in CC 4.1. We zouden het alleen moeten gebruiken als we niet kunnen upgraden naar de nieuwere versie.

4.2. MultiValuedMap

De opvolger van MultiMap is de org.apache.commons.collections4.MultiValuedMap koppel. Het heeft meerdere implementaties klaar om te worden gebruikt.

Laten we eens kijken hoe we onze meerdere waarden kunnen opslaan in een ArrayList, die duplicaten behoudt:

MultiValuedMap map = nieuwe ArrayListValuedHashMap (); map.put ("key1", "value1"); map.put ("key1", "value2"); map.put ("key1", "value2"); assertThat ((Verzameling) map.get ("key1")) .containsExactly ("waarde1", "waarde2", "waarde2"); 

Als alternatief kunnen we een HashSet, die duplicaten laat vallen:

MultiValuedMap-kaart = nieuwe HashSetValuedHashMap (); map.put ("key1", "value1"); map.put ("key1", "value1"); assertThat ((Verzameling) map.get ("key1")) .containsExactly ("waarde1"); 

Beide bovenstaande implementaties zijn niet thread-safe.

Laten we eens kijken hoe we de UnmodifiableMultiValuedMap decorateur om ze onveranderlijk te maken:

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

5. Guave Multimap

Guava is de Google Core Libraries voor Java API.

De com.google.common.collect.Multimap interface is er sinds versie 2. Op het moment van schrijven is de laatste release de 25, maar sinds versie 23 is het opgesplitst in verschillende branches voor jre en android (25,0 jre en 25.0-android), gebruiken we nog steeds versie 23 voor onze voorbeelden.

Laten we beginnen met het importeren van Guava in ons project:

 com.google.guava guave 23.0 

Guava volgde vanaf het begin het pad van de meerdere implementaties.

De meest voorkomende is de com.google.common.collect.ArrayListMultimap, die een Hash kaart ondersteund door een ArrayList voor elke waarde:

Multimap-kaart = ArrayListMultimap.create (); map.put ("key1", "value2"); map.put ("key1", "value1"); assertThat ((Verzameling) map.get ("key1")) .containsExactly ("waarde2", "waarde1"); 

Zoals altijd zouden we de voorkeur moeten geven aan de onveranderlijke implementaties van de Multimap-interface: com.google.common.collect.ImmutableListMultimap en com.google.common.collect.ImmutableSetMultimap.

5.1. Gemeenschappelijke kaartimplementaties

Als we een specifiek nodig hebben Kaart implementatie, is het eerste dat u moet doen controleren of het bestaat, omdat Guava het waarschijnlijk al heeft geïmplementeerd.

We kunnen bijvoorbeeld de com.google.common.collect.LinkedHashMultimap, waarmee de volgorde van invoegen van sleutels en waarden behouden blijft:

Multimap-kaart = LinkedHashMultimap.create (); map.put ("key1", "value3"); map.put ("key1", "value1"); map.put ("key1", "value2"); assertThat ((Verzameling) map.get ("key1")) .containsExactly ("waarde3", "waarde1", "waarde2"); 

Als alternatief kunnen we een com.google.common.collect.TreeMultimap, die sleutels en waarden in hun natuurlijke volgorde herhaalt:

Multimap-kaart = TreeMultimap.create (); map.put ("key1", "value3"); map.put ("key1", "value1"); map.put ("key1", "value2"); assertThat ((Verzameling) map.get ("key1")) .containsExactly ("waarde1", "waarde2", "waarde3"); 

5.2. Onze gewoonte smeden MultiMap

Er zijn veel andere implementaties beschikbaar.

Het kan echter zijn dat we een Kaart en / of a Lijst nog niet geimplementeerd.

Gelukkig heeft Guava een fabrieksmethode waarmee we het kunnen doen: de Multimap.newMultimap ().

6. Conclusie

We hebben gezien hoe u meerdere waarden voor een sleutel in een kaart op alle belangrijke bestaande manieren kunt opslaan.

We hebben de meest populaire implementaties van Apache Commons Collections en Guava onderzocht, die waar mogelijk de voorkeur verdienen boven aangepaste oplossingen.

Zoals altijd is de volledige broncode beschikbaar op Github.