Colectores de Java 8 toMap

1. Introducción

En este tutorial rápido, hablaremos sobre el método toMap () de la clase Collectors . Lo usaremos para recopilar Stream s en una instancia de Map .

Para todos los ejemplos cubiertos aquí, usaremos una lista de libros como punto de partida y la transformaremos en diferentes implementaciones de Map .

2. Lista a mapa

Comenzaremos con el caso más simple, transformando una Lista en un Mapa .

Nuestra clase de libros se define como:

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

Y crearemos una lista de libros para validar nuestro código:

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

Para este escenario usaremos la siguiente sobrecarga del método toMap () :

Collector
    
      toMap(Function keyMapper, Function valueMapper)
    

Con toMap , podemos indicar estrategias sobre cómo obtener la clave y el valor del mapa:

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

Y podemos validarlo fácilmente con:

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

3. Resolución de conflictos clave

El ejemplo anterior funcionó bien, pero ¿qué pasaría si hay una clave duplicada?

Imaginemos que codificamos nuestro mapa por año de publicación de cada libro :

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

Dada nuestra lista anterior de libros, veríamos una IllegalStateException :

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

Para resolverlo, necesitamos usar un método diferente con un parámetro adicional, mergeFunction :

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

Introduzcamos una función de combinación que indica que, en el caso de una colisión, mantenemos la entrada existente:

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

O, en otras palabras, obtenemos el comportamiento de primero ganar:

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

4. Otros tipos de mapas

De forma predeterminada, un método toMap () devolverá un HashMap .

Pero, ¿podemos devolver diferentes implementaciones de mapas ? La respuesta es sí:

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

Donde mapSupplier es una función que devuelve un mapa nuevo y vacío con los resultados.

4.1. Lista a ConcurrentMap

Tomemos el mismo ejemplo anterior y agreguemos una función mapSupplier para devolver un ConcurrentHashMap:

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

Sigamos y probemos nuestro código:

@Test public void whenCreateConcurrentHashMap() { assertTrue(convertToMap.listToConcurrentMap(bookList) instanceof ConcurrentHashMap); }
4.2. Mapa ordenado

Por último, veamos cómo devolver un mapa ordenado. Para eso usaremos un TreeMap como parámetro mapSupplier .

Debido a que un TreeMap se ordena de acuerdo con el orden natural de sus claves de forma predeterminada, no tenemos que ordenar explícitamente los libros nosotros mismos:

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

Entonces, en nuestro caso, el TreeMap devuelto se ordenará en orden alfabético por el nombre del libro:

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

En este artículo, analizamos el método toMap () de la clase Collectors . Nos permite crear un nuevo mapa a partir de un arroyo . También aprendimos cómo resolver conflictos clave y crear diferentes implementaciones de mapas.

Como siempre, el código está disponible en GitHub.