Hvordan gemmes duplikatnøgler på et kort i Java?

1. Oversigt

I denne vejledning skal vi undersøge de tilgængelige muligheder for håndtering af en Kort med duplikatnøgler eller med andre ord a Kort som tillader lagring af flere værdier for en enkelt nøgle.

2. Standardkort

Java har flere implementeringer af grænsefladen Kort, hver med sine egne detaljer.

Imidlertid, ingen af ​​de eksisterende Java-kernekortimplementeringer tillader en Kort til at håndtere flere værdier for en enkelt nøgle.

Som vi kan se, hvis vi forsøger at indsætte to værdier for den samme nøgle, gemmes den anden værdi, mens den første slettes.

Det vil også blive returneret (ved enhver korrekt implementering af put (K-tast, V-værdi) metode):

Kortkort = nyt HashMap (); assertThat (map.put ("key1", "value1")). er EqualTo (null); assertThat (map.put ("key1", "value2")). er EqualTo ("value1"); assertThat (map.get ("key1")). er EqualTo ("value2"); 

Hvordan kan vi nå den ønskede adfærd?

3. Indsamling som værdi

Det er klart, at man bruger en Kollektion for enhver værdi af vores Kort ville gøre jobbet:

Kort kort = nyt HashMap (); Liste liste = ny ArrayList (); map.put ("key1", liste); map.get ("key1"). tilføj ("value1"); map.get ("key1"). tilføj ("value2"); assertThat (map.get ("key1"). get (0)). isEqualTo ("value1"); assertThat (map.get ("key1"). get (1)). isEqualTo ("value2"); 

Denne detaljerede løsning har dog flere ulemper og er tilbøjelig til fejl. Det indebærer, at vi er nødt til at instantiere en Kollektion for hver værdi skal du kontrollere, om den er til stede, før du tilføjer eller fjerner en værdi, slet den manuelt, når der ikke er nogen værdier tilbage osv.

Fra Java 8 kunne vi udnytte beregne () metoder og forbedre det:

Kort kort = nyt 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"); 

Selvom dette er noget, der er værd at vide, bør vi undgå det, medmindre vi har en meget god grund til ikke at gøre det, som f.eks. Restriktive virksomhedspolitikker, der forhindrer os i at bruge tredjepartsbiblioteker.

Ellers inden du skriver vores egen skik Kort implementering og genopfinde hjulet, skal vi vælge mellem de forskellige tilgængelige muligheder uden for kassen.

4. Apache Commons samlinger

Som sædvanligt, Apache har en løsning på vores problem.

Lad os starte med at importere den seneste udgave af Almindelige samlinger (CC fra nu af):

 org.apache.commons commons-collection4 4.1 

4.1. MultiMap

Det org.apache.commons.collections4.MultiMap interface definerer et kort, der indeholder en samling værdier mod hver nøgle.

Det er implementeret af org.apache.commons.collections4.map.MultiValueMap klasse, der automatisk håndterer det meste af kogepladen under emhætten:

MultiMap-kort = nyt MultiValueMap (); map.put ("nøgle1", "værdi1"); map.put ("key1", "value2"); assertThat ((Collection) map.get ("key1")) .contains ("value1", "value2"); 

Mens denne klasse er tilgængelig siden CC 3.2, det er ikke trådsikkertog det er udfaset i CC 4.1. Vi bør kun bruge det, når vi ikke kan opgradere til den nyere version.

4.2. MultiValuedMap

Efterfølgeren til MultiMap er org.apache.commons.collections4.MultiValuedMap interface. Den har flere implementeringer klar til brug.

Lad os se, hvordan vi gemmer vores flere værdier i en ArrayList, der bevarer dubletter:

MultiValuedMap-kort = nyt ArrayListValuedHashMap (); map.put ("nøgle1", "værdi1"); map.put ("key1", "value2"); map.put ("key1", "value2"); assertThat ((Collection) map.get ("key1")) .containsExactly ("værdi1", "værdi2", "værdi2"); 

Alternativt kunne vi bruge en HashSet, der taber duplikater:

MultiValuedMap-kort = nyt HashSetValuedHashMap (); map.put ("nøgle1", "værdi1"); map.put ("nøgle1", "værdi1"); assertThat ((Collection) map.get ("key1")) .containsExactly ("value1"); 

Begge de ovenstående implementeringer er ikke trådsikre.

Lad os se, hvordan vi kan bruge UnmodifiableMultiValuedMap dekoratør for at gøre dem uforanderlige:

@Test (forventet = UnsupportedOperationException.class) offentlig ugyldighed givenUnmodifiableMultiValuedMap_whenInserting_thenThrowingException () {MultiValuedMap map = new ArrayListValuedHashMap (); map.put ("nøgle1", "værdi1"); map.put ("key1", "value2"); MultiValuedMap immutableMap = MultiMapUtils.unmodifiableMultiValuedMap (kort); immutableMap.put ("key1", "value3"); } 

5. Guava Multimap

Guava er Google Core Libraries for Java API.

Det com.google.common.collect.Multimap interface er der siden version 2. I skrivende stund er den seneste udgivelse den 25, men siden version 23 er den blevet opdelt i forskellige grene til jre og Android (25,0-jre og 25.0-android), bruger vi stadig version 23 til vores eksempler.

Lad os starte med at importere Guava på vores projekt:

 com.google.guava guava 23.0 

Guava fulgte stien til de mange implementeringer siden starten.

Den mest almindelige er com.google.common.collect.ArrayListMultimap, der bruger en HashMap bakket op af en ArrayList for hver værdi:

Multimap-kort = ArrayListMultimap.create (); map.put ("key1", "value2"); map.put ("nøgle1", "værdi1"); assertThat ((Collection) map.get ("key1")) .containsExactly ("værdi2", "værdi1"); 

Som altid bør vi foretrække de uforanderlige implementeringer af Multimap-grænsefladen: com.google.common.collect.ImmutableListMultimap og com.google.common.collect.ImmutableSetMultimap.

5.1. Almindelige kortimplementeringer

Når vi har brug for et specifikt Kort implementering, er den første ting at gøre, at kontrollere, om den findes, fordi Guava sandsynligvis allerede har implementeret den.

For eksempel kan vi bruge com.google.common.collect.LinkedHashMultimap, som bevarer indsættelsesrækkefølgen for nøgler og værdier:

Multimap-kort = LinkedHashMultimap.create (); map.put ("key1", "value3"); map.put ("nøgle1", "værdi1"); map.put ("key1", "value2"); assertThat ((Collection) map.get ("key1")) .containsExactly ("værdi3", "værdi1", "værdi2"); 

Alternativt kan vi bruge en com.google.common.collect.TreeMultimap, som gentager nøgler og værdier i deres naturlige rækkefølge:

Multimap-kort = TreeMultimap.create (); map.put ("key1", "value3"); map.put ("nøgle1", "værdi1"); map.put ("key1", "value2"); assertThat ((Collection) map.get ("key1")) .containsExactly ("værdi1", "værdi2", "værdi3"); 

5.2. Smedning af vores brugerdefinerede MultiMap

Mange andre implementeringer er tilgængelige.

Vi ønsker dog måske at dekorere en Kort og / eller en Liste endnu ikke implementeret.

Heldigvis har Guava en fabriksmetode, der giver os mulighed for at gøre det: Multimap.newMultimap ().

6. Konklusion

Vi har set, hvordan man gemmer flere værdier for en nøgle på et kort på alle de vigtigste eksisterende måder.

Vi har undersøgt de mest populære implementeringer af Apache Commons Collections og Guava, som bør foretrækkes frem for tilpassede løsninger, når det er muligt.

Som altid er den fulde kildekode tilgængelig på Github.