Перейти к основному содержимому

Работа с картами с использованием потоков

· 4 мин. чтения

1. Введение

В этом руководстве мы обсудим несколько примеров использования Java Stream ` для работы [с ](/lessons/b/-java-8-streams-introduction)Map[ . ](/lessons/b/-java-hashmap) Стоит отметить, что некоторые из этих упражнений могут быть решены с использованием двунаправленной структуры данных Map` , но здесь нас интересует функциональный подход.

Во-первых, мы объясним основную идею, которую будем использовать для работы с Maps и Stream s. Затем мы представим несколько разных проблем, связанных с Maps , и их конкретные решения с использованием Stream s.

2. Основная идея

Главное, на что следует обратить внимание, это то, что Stream — это последовательности элементов, которые можно легко получить из Collection .

Карты имеют другую структуру, с сопоставлением ключей со значениями без последовательности. Однако это не означает, что мы не можем преобразовать структуру карты в различные последовательности, которые затем позволят нам естественным образом работать с Stream API.

Давайте посмотрим, как можно получить различные Collection из Map , которые затем можно преобразовать в Stream :

Map<String, Integer> someMap = new HashMap<>();

Мы можем получить набор пар ключ-значение:

Set<Map.Entry<String, Integer>> entries = someMap.entrySet();

Мы также можем получить набор ключей, связанный с Map :

Set<String> keySet = someMap.keySet();

Или мы могли бы работать напрямую с набором значений:

Collection<Integer> values = someMap.values();

Каждый из них дает нам точку входа для обработки этих коллекций путем получения из них потоков:

Stream<Map.Entry<String, Integer>> entriesStream = entries.stream();
Stream<Integer> valuesStream = values.stream();
Stream<String> keysStream = keySet.stream();

3. Получение ключей карты с помощью Stream s

3.1. Входные данные

Предположим, у нас есть карта :

Map<String, String> books = new HashMap<>();
books.put(
"978-0201633610", "Design patterns : elements of reusable object-oriented software");
books.put(
"978-1617291999", "Java 8 in Action: Lambdas, Streams, and functional-style programming");
books.put("978-0134685991", "Effective Java");

Мы заинтересованы в поиске ISBN для книги под названием «Эффективная Java».

3.2. Получение совпадения

Поскольку названия книги не может быть на нашей карте , мы хотим иметь возможность указать, что для нее нет связанного ISBN. Мы можем использовать необязательный параметр , чтобы выразить это:

Предположим для этого примера, что нас интересует любой ключ для книги, соответствующей этому названию:

Optional<String> optionalIsbn = books.entrySet().stream()
.filter(e -> "Effective Java".equals(e.getValue()))
.map(Map.Entry::getKey)
.findFirst();

assertEquals("978-0134685991", optionalIsbn.get());

Проанализируем код. Во- первых, мы получаем entrySet из Map , как мы видели ранее.

Мы хотим рассматривать только записи с заголовком «Эффективная Java», поэтому первой промежуточной операцией будет фильтр .

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

Поскольку нам нужен только один результат, мы можем применить терминальную операцию findFirst() , которая предоставит начальное значение в потоке как необязательный объект.

Давайте рассмотрим случай, когда заголовок не существует:

Optional<String> optionalIsbn = books.entrySet().stream()
.filter(e -> "Non Existent Title".equals(e.getValue()))
.map(Map.Entry::getKey).findFirst();

assertEquals(false, optionalIsbn.isPresent());

3.3. Получение нескольких результатов

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

Чтобы вернуть несколько результатов, давайте добавим следующую книгу на нашу карту :

books.put("978-0321356680", "Effective Java: Second Edition");

Так что теперь, если мы найдем все книги, начинающиеся с «Effective Java», мы получим более одного результата:

List<String> isbnCodes = books.entrySet().stream()
.filter(e -> e.getValue().startsWith("Effective Java"))
.map(Map.Entry::getKey)
.collect(Collectors.toList());

assertTrue(isbnCodes.contains("978-0321356680"));
assertTrue(isbnCodes.contains("978-0134685991"));

Что мы сделали в этом случае, так это заменили условие фильтра, чтобы проверить, начинается ли значение на карте с «Эффективная Java» вместо сравнения на равенство строк .

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

4. Получение значений Map с помощью Stream s

Теперь давайте сосредоточимся на другой проблеме с картами. Вместо того, чтобы получать ISBN на основе заголовков , мы попытаемся получить заголовки на основе ISBN.

Давайте использовать исходную карту . Мы хотим найти заголовки с ISBN, начинающимся с «978-0».

List<String> titles = books.entrySet().stream()
.filter(e -> e.getKey().startsWith("978-0"))
.map(Map.Entry::getValue)
.collect(Collectors.toList());

assertEquals(2, titles.size());
assertTrue(titles.contains(
"Design patterns : elements of reusable object-oriented software"));
assertTrue(titles.contains("Effective Java"));

Это решение похоже на решения нашего предыдущего набора задач; мы передаем набор записей, а затем фильтруем, сопоставляем и собираем.

Также, как и раньше, если бы мы хотели вернуть только первое совпадение, то после метода map мы могли бы вызвать метод findFirst() вместо того, чтобы собирать все результаты в List .

5. Вывод

В этой статье мы продемонстрировали, как обрабатывать Map функциональным образом .

В частности, мы видели, что как только мы переключаемся на использование связанных коллекций с Maps , обработка с использованием Stream становится намного проще и интуитивно понятна.

Разумеется, все примеры из этой статьи можно найти в проекте GitHub .