Collectionneurs Java 8 toMap

1. Introduction

Dans ce rapide tutoriel, nous allons parler de la méthode toMap () de la classe Collectors . Nous l'utiliserons pour collecter les Stream dans une instance de Map .

Pour tous les exemples abordés ici, nous utiliserons une liste de livres comme point de départ et la transformerons en différentes implémentations de Map .

2. Liste à mapper

Nous allons commencer par le cas le plus simple, en transformant une liste en une carte .

Notre classe Book est définie comme:

class Book { private String name; private int releaseYear; private String isbn; // getters and setters }

Et nous allons créer une liste de livres pour valider notre code:

List bookList = new ArrayList(); bookList.add(new Book("The Fellowship of the Ring", 1954, "0395489318")); bookList.add(new Book("The Two Towers", 1954, "0345339711")); bookList.add(new Book("The Return of the King", 1955, "0618129111"));

Pour ce scénario, nous utiliserons la surcharge suivante de la méthode toMap () :

Collector
    
      toMap(Function keyMapper, Function valueMapper)
    

Avec toMap , nous pouvons indiquer des stratégies pour obtenir la clé et la valeur de la carte:

public Map listToMap(List books) { return books.stream().collect(Collectors.toMap(Book::getIsbn, Book::getName)); }

Et nous pouvons facilement valider que cela fonctionne avec:

@Test public void whenConvertFromListToMap() { assertTrue(convertToMap.listToMap(bookList).size() == 3); }

3. Résolution des conflits clés

L'exemple ci-dessus a bien fonctionné, mais que se passerait-il s'il y avait une clé en double?

Imaginons que nous ayons défini notre carte par année de sortie de chaque livre :

public Map listToMapWithDupKeyError(List books) { return books.stream().collect( Collectors.toMap(Book::getReleaseYear, Function.identity())); }

Compte tenu de notre liste de livres précédente, nous verrions une exception IllegalStateException :

@Test(expected = IllegalStateException.class) public void whenMapHasDuplicateKey_without_merge_function_then_runtime_exception() { convertToMap.listToMapWithDupKeyError(bookList); }

Pour le résoudre, nous devons utiliser une méthode différente avec un paramètre supplémentaire, le mergeFunction :

Collector toMap(Function keyMapper, Function valueMapper, BinaryOperator mergeFunction) 

Introduisons une fonction de fusion qui indique que, en cas de collision, on garde l'entrée existante:

public Map listToMapWithDupKey(List books) { return books.stream().collect(Collectors.toMap(Book::getReleaseYear, Function.identity(), (existing, replacement) -> existing)); }

Ou, en d'autres termes, nous obtenons un comportement premier gagnant:

@Test public void whenMapHasDuplicateKeyThenMergeFunctionHandlesCollision() { Map booksByYear = convertToMap.listToMapWithDupKey(bookList); assertEquals(2, booksByYear.size()); assertEquals("0395489318", booksByYear.get(1954).getIsbn()); }

4. Autres types de carte

Par défaut, une méthode toMap () retournera un HashMap .

Mais pouvons-nous renvoyer différentes implémentations de Map ? La réponse est oui:

Collector toMap(Function keyMapper, Function valueMapper, BinaryOperator mergeFunction, Supplier mapSupplier)

mapSupplier est une fonction qui renvoie une nouvelle carte vide avec les résultats.

4.1. Liste à ConcurrentMap

Prenons le même exemple que ci-dessus et ajoutons une fonction mapSupplier pour renvoyer un ConcurrentHashMap:

public Map listToConcurrentMap(List books) { return books.stream().collect(Collectors.toMap(Book::getReleaseYear, Function.identity(), (o1, o2) -> o1, ConcurrentHashMap::new)); }

Continuons et testons notre code:

@Test public void whenCreateConcurrentHashMap() { assertTrue(convertToMap.listToConcurrentMap(bookList) instanceof ConcurrentHashMap); }
4.2. Carte triée

Enfin, voyons comment renvoyer une carte triée. Pour cela, nous utiliserons un TreeMap comme paramètre mapSupplier .

Étant donné qu'un TreeMap est trié par défaut selon l'ordre naturel de ses clés, nous n'avons pas à trier explicitement les livres nous-mêmes:

public TreeMap listToSortedMap(List books) { return books.stream() .collect( Collectors.toMap(Book::getName, Function.identity(), (o1, o2) -> o1, TreeMap::new)); }

Donc dans notre cas, le TreeMap retourné sera trié dans l'ordre alphabétique par le nom du livre:

@Test public void whenMapisSorted() { assertTrue(convertToMap.listToSortedMap(bookList).firstKey().equals( "The Fellowship of the Ring")); }
5. Conclusion

Dans cet article, nous avons examiné la méthode toMap () de la classe Collectors . Cela nous permet de créer une nouvelle carte à partir d'un flux . Nous avons également appris à résoudre les conflits clés et à créer différentes implémentations de carte.

Comme toujours, le code est disponible sur GitHub.