ToMap do Java 8 Collectors

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 keyMapper,
  Function 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 keyMapper,
  Function 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 keyMapper,
  Function 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.