Рубрики
Без рубрики

Java 8 Коллекторы toMap

Узнайте, как использовать метод toMap() класса Collectors.

Автор оригинала: Rodrigo Graciano.

1. Обзор

В этом кратком руководстве мы поговорим о методе toMap() класса Collectors . Мы будем использовать его для сбора Stream s в экземпляр Map .

Для всех примеров, рассмотренных здесь, мы будем использовать список книг в качестве отправной точки и преобразовывать его в различные Карта реализации.

Дальнейшее чтение:

Руководство по сборщикам Java 8

Соберите поток Java в неизменяемую коллекцию

Новые сборщики потоков в Java 9

2. Список на карте

Мы начнем с самого простого случая, преобразовав Список в Карту .

Вот как мы определяем наш Book класс:

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

И мы создадим список книг для проверки нашего кода:

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

Для этого сценария мы будем использовать следующую перегрузку метода to Map() :

Collector> toMap(Function keyMapper,
  Function valueMapper)

С помощью to Map мы можем указать стратегии получения ключа и значения для карты :

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

И мы можем легко подтвердить, что это работает:

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

3. Решение Ключевых Конфликтов

Приведенный выше пример работал хорошо, но что произойдет с дубликатом ключа?

Давайте представим, что мы настроили нашу Карту по каждой Книге году выпуска:

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

Учитывая наш предыдущий список книг, мы увидим исключение IllegalStateException :

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

Чтобы решить эту проблему, нам нужно использовать другой метод с дополнительным параметром, функцией merge :

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

Давайте введем функцию слияния, которая указывает, что в случае столкновения мы сохраняем существующую запись:

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

Или, другими словами, мы получаем поведение первой победы:

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

4. Другие Типы Карт

По умолчанию метод toMap() возвращает HashMap .

Но мы можем вернуть разные Map реализации :

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

где поставщик карты – это функция, которая возвращает новую пустую карту с результатами.

4.1. Список в ConcurrentMap

Давайте возьмем тот же пример и добавим функцию map для возврата ConcurrentHashMap :

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

Мы продолжим и протестируем наш код:

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

4.2. Отсортированная карта

Наконец, давайте посмотрим, как вернуть отсортированную карту. Для этого мы будем использовать TreeMap в качестве поставщика карт параметра.

Поскольку TreeMap по умолчанию сортируется в соответствии с естественным порядком его ключей, нам не нужно явно сортировать книги самостоятельно:

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

Таким образом, в нашем случае возвращенная Карта дерева будет отсортирована в алфавитном порядке по названию книги:

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

5. Заключение

В этой статье мы рассмотрели метод toMap() класса Collectors . Это позволяет нам создать новую карту из Потока .

Мы также узнали, как разрешать ключевые конфликты и создавать различные реализации карт.

Как всегда, код доступен на GitHub .