Comment stocker les clés en double dans une carte en Java?

Comment stocker des clés en double dans une carte en Java?

1. Vue d'ensemble

Dans ce didacticiel, nous allons explorer les options disponibles pour gérer unMap avec des clés en double ou, en d'autres termes, unMap qui permet de stocker plusieurs valeurs pour une seule clé.

2. Cartes standard

Java a plusieurs implémentations de l'interfaceMap, chacune avec ses propres particularités.

Cependant,none of the existing Java core Map implementations allow a Map to handle multiple values for a single key.

Comme nous pouvons le constater, si nous essayons d'insérer deux valeurs pour la même clé, la deuxième valeur sera stockée, tandis que la première sera supprimée.

Il sera également retourné (par chaque implémentation correcte de la méthodeput(K key, V value)):

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

Comment pouvons-nous atteindre le comportement souhaité alors?

3. Collection comme valeur

De toute évidence, utiliser unCollection pour chaque valeur de nosMap ferait l'affaire:

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

Cependant, cette solution détaillée présente de nombreux inconvénients et est sujette aux erreurs. Cela implique que nous devons instancier unCollection pour chaque valeur, vérifier sa présence avant d'ajouter ou de supprimer une valeur, la supprimer manuellement lorsqu'il ne reste aucune valeur, etc.

Depuis Java 8, nous pourrions exploiter les méthodescompute() et les améliorer:

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

Bien que ce soit quelque chose d'intéressant à savoir, nous devrions l'éviter à moins d'avoir une très bonne raison de ne pas le faire, comme les règles restrictives de l'entreprise qui nous empêchent d'utiliser des bibliothèques tierces.

Sinon, avant d'écrire notre propre implémentation personnalisée deMapet de réinventer la roue, nous devrions choisir parmi les nombreuses options disponibles prêtes à l'emploi.

4. Collections de la famille Apache Commons

Comme d'habitude,Apache a une solution à notre problème.

Commençons par importer la dernière version deCommon Collections (CC à partir de maintenant):


  org.apache.commons
  commons-collections4
  4.1

4.1. MultiMap

L'interfaceorg.apache.commons.collections4.MultiMap définit un Map qui contient une collection de valeurs pour chaque clé.

Il est implémenté par la classeorg.apache.commons.collections4.map.MultiValueMap, qui gère automatiquement la plupart des passe-partout sous le capot:

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

Bien que cette classe soit disponible depuis CC 3.2,it’s not thread-safe etit’s been deprecated in CC 4.1. Nous ne devons l'utiliser que lorsque nous ne pouvons pas passer à la nouvelle version.

4.2. MultiValuedMap

Le successeur deMultiMap est l'interfaceorg.apache.commons.collections4.MultiValuedMap. Il a plusieurs implémentations prêtes à être utilisées.

Voyons comment stocker nos multiples valeurs dans unArrayList, qui conserve les doublons:

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

Alternativement, nous pourrions utiliser unHashSet, qui supprime les doublons:

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

Les deuxabove implementations are not thread-safe.

Voyons comment nous pouvons utiliser le décorateurUnmodifiableMultiValuedMap pour les rendre immuables:

@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. GoyaveMultimap

Guava est l'API Google Core Bibliothèques pour Java.

L'interfacecom.google.common.collect.Multimap existe depuis la version 2. Au moment de la rédaction de cet article, la dernière version est la 25, mais comme après la version 23, elle a été divisée en différentes branches pourjre etandroid (25.0-jre et25.0-android), nous J'utiliserai toujours la version 23 pour nos exemples.

Commençons par importer Guava sur notre projet:


  com.google.guava
  guava
  23.0

Guava a suivi le chemin des multiples implémentations depuis le début.

Le plus courant est lecom.google.common.collect.ArrayListMultimap, qui utilise unHashMap soutenu par unArrayList pour chaque valeur:

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

Comme toujours, nous devrions préférer les implémentations immuables de l'interface Multimap:com.google.common.collect.ImmutableListMultimap etcom.google.common.collect.ImmutableSetMultimap.

5.1. Implémentations de cartes communes

Lorsque nous avons besoin d'une implémentation spécifique deMap, la première chose à faire est de vérifier si elle existe, car probablement Guava l'a déjà implémentée.

Par exemple, nous pouvons utiliser lecom.google.common.collect.LinkedHashMultimap, qui préserve l'ordre d'insertion des clés et des valeurs:

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

Alternativement, nous pouvons utiliser uncom.google.common.collect.TreeMultimap, qui itère les clés et les valeurs dans leur ordre naturel:

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. Forger nosMultiMap personnalisés

Beaucoup d'autres implémentations sont disponibles.

Cependant, nous pouvons souhaiter décorer unMap et / ou unList pas encore implémentés.

Heureusement, Guava a une méthode d'usine nous permettant de le faire: lesMultimap.newMultimap().

6. Conclusion

Nous avons vu comment stocker plusieurs valeurs pour une clé dans une carte de toutes les principales méthodes existantes.

Nous avons exploré les implémentations les plus populaires d'Apache Commons Collections et Guava, qui devraient être préférées aux solutions personnalisées lorsque cela est possible.

Comme toujours, le code source complet est disponibleover on Github.