ToMap do Java 8 Collectors
1. Introdução
Neste tutorial rápido, vamos falar sobre o métodotoMap() da classeCollectors. Vamos usá-lo para coletarStreams em uma instânciaMap.
Para todos os exemplos cobertos aqui, vamos usar uma lista de livros como ponto de partida e transformá-la em diferentes implementações deMap.
2. List aMap
Começaremos com o caso mais simples, transformando aList emMap.
Nossa classeBook é definida como:
class Book {
private String name;
private int releaseYear;
private String isbn;
// getters and setters
}
E vamos criar uma lista de livros para validar nosso 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 cenário, usaremos a seguinte sobrecarga do métodotoMap():
Collector> toMap(Function super T, ? extends K> keyMapper,
Function super T, ? extends U> valueMapper)
ComtoMap, podemos indicar estratégias de como obter a chave e o valor para o mapa:
public Map listToMap(List books) {
return books.stream().collect(Collectors.toMap(Book::getIsbn, Book::getName));
}
E podemos validar facilmente que funciona com:
@Test
public void whenConvertFromListToMap() {
assertTrue(convertToMap.listToMap(bookList).size() == 3);
}
3. Solução de conflitos importantes
O exemplo acima funcionou bem, mas o que aconteceria se houvesse uma chave duplicada?
Vamos imaginar que ajustamos nossoMap para cada ano de lançamento deBook:
public Map listToMapWithDupKeyError(List books) {
return books.stream().collect(Collectors.toMap(Book::getReleaseYear, Function.identity()));
}
Dada nossa lista anterior de livros, veríamos umIllegalStateException:
@Test(expected = IllegalStateException.class)
public void whenMapHasDuplicateKey_without_merge_function_then_runtime_exception() {
convertToMap.listToMapWithDupKeyError(bookList);
}
Para resolver isso, precisamos usar um método diferente com um parâmetro adicional, omergeFunction:
Collector toMap(Function super T, ? extends K> keyMapper,
Function super T, ? extends U> valueMapper,
BinaryOperator mergeFunction)
Vamos apresentar uma função de mesclagem que indica que, no caso de uma colisão, mantemos a entrada existente:
public Map listToMapWithDupKey(List books) {
return books.stream().collect(Collectors.toMap(Book::getReleaseYear, Function.identity(),
(existing, replacement) -> existing));
}
Ou, em outras palavras, obtemos um comportamento de primeira vitória:
@Test
public void whenMapHasDuplicateKeyThenMergeFunctionHandlesCollision() {
Map booksByYear = convertToMap.listToMapWithDupKey(bookList);
assertEquals(2, booksByYear.size());
assertEquals("0395489318", booksByYear.get(1954).getIsbn());
}
4. Outros tipos de mapa
Por padrão, um métodotoMap() retornará umHashMap.
Mas podemos retornar implementações deMap diferentes? A resposta é sim:
Collector toMap(Function super T, ? extends K> keyMapper,
Function super T, ? extends U> valueMapper,
BinaryOperator mergeFunction,
Supplier mapSupplier)
OndemapSupplier é uma função que retorna um novoMap vazio com os resultados.
4.1. List aConcurrentMap
Vamos pegar o mesmo exemplo acima e adicionar uma funçãomapSupplier para retornar umConcurrentHashMap:
public Map listToConcurrentMap(List books) {
return books.stream().collect(Collectors.toMap(Book::getReleaseYear, Function.identity(),
(o1, o2) -> o1, ConcurrentHashMap::new));
}
Vamos testar nosso código:
@Test
public void whenCreateConcurrentHashMap() {
assertTrue(convertToMap.listToConcurrentMap(bookList) instanceof ConcurrentHashMap);
}
4.2. OrdenadoMap
Por fim, vamos ver como retornar um mapa classificado. Para isso, precisaremos classificar uma lista e usarTreeMap como parâmetromapSupplier:
public TreeMap listToSortedMap(List books) {
return books.stream()
.sorted(Comparator.comparing(Book::getName))
.collect(Collectors.toMap(Book::getName, Function.identity(), (o1, o2) -> o1, TreeMap::new));
}
O código acima irá classificar a lista com base no nome do livro e, em seguida, coletar os resultados para umTreeMap:
@Test
public void whenMapisSorted() {
assertTrue(convertToMap.listToSortedMap(bookList).firstKey().equals("The Fellowship of the Ring"));
}
5. Conclusão
Neste artigo, examinamos o métodotoMap() da classeCollectors. Isso nos permite criar um novoMap a partir deStream. Também aprendemos como resolver conflitos importantes e criar diferentes implementações de mapas.
Como sempre, o código está disponívelover on GitHub.