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 .