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

Введение в StreamEx

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

1. Обзор

Одной из самых захватывающих функций Java 8 является Stream API , который, проще говоря, представляет собой мощный инструмент для обработки последовательностей элементов.

StreamEx — это библиотека, которая предоставляет дополнительные функции для стандартного Stream API, а также повышает производительность.

Вот несколько основных особенностей:

  • Более короткие и удобные способы выполнения повседневных задач
  • 100% совместимость с оригинальными потоками JDK.
  • Удобство для параллельной обработки: любая новая функция максимально использует преимущества параллельных потоков.
  • Производительность и минимальные накладные расходы. Если StreamEx позволяет решить задачу с использованием меньшего количества кода по сравнению со стандартным Stream, он не должен быть значительно медленнее обычного (а иногда даже быстрее)

В этом руководстве мы представим некоторые функции StreamEx API.

2. Настройка примера

Чтобы использовать StreamEx , нам нужно добавить следующую зависимость в pom.xml :

<dependency>
<groupId>one.util</groupId>
<artifactId>streamex</artifactId>
<version>0.6.5</version>
</dependency>

Последнюю версию библиотеки можно найти на Maven Central .

В этом руководстве мы будем использовать простой класс User :

public class User {
int id;
String name;
Role role = new Role();

// standard getters, setters, and constructors
}

И простой класс ролей :

public class Role {
}

3. Методы быстрого доступа к коллекторам

Одной из самых популярных терминальных операций Streams является операция сбора ; это позволяет переупаковывать элементы Stream в коллекцию по нашему выбору.

Проблема в том, что код может стать излишне многословным для простых сценариев:

users.stream()
.map(User::getName)
.collect(Collectors.toList());

3.1. Сбор в коллекцию

Теперь, с StreamEx, нам не нужно предоставлять Collector , чтобы указать, что нам нужен List , Set, Map, InmutableList и т. д .:

List<String> userNames = StreamEx.of(users)
.map(User::getName)
.toList();

Операция сбора по-прежнему доступна в API, если мы хотим выполнить что-то более сложное, чем брать элементы из потока и помещать их в коллекцию.

3.2. Продвинутые коллекционеры

Другое сокращение — groupingBy :

Map<Role, List<User>> role2users = StreamEx.of(users)
.groupingBy(User::getRole);

Это создаст карту с типом ключа, указанным в ссылке на метод, создавая что-то похожее на группу с помощью операции в SQL.

Используя простой Stream API, нам нужно написать:

Map<Role, List<User>> role2users = users.stream()
.collect(Collectors.groupingBy(User::getRole));

Аналогичную сокращенную форму можно найти для Collectors.joining():

StreamEx.of(1, 2, 3)
.joining("; "); // "1; 2; 3"

Который принимает все элементы в потоке , a создает строку , объединяющую их все.

4. Добавление, удаление и выбор элементов

В некоторых сценариях у нас есть список объектов разных типов, и нам нужно отфильтровать их по типу:

List usersAndRoles = Arrays.asList(new User(), new Role());
List<Role> roles = StreamEx.of(usersAndRoles)
.select(Role.class)
.toList();

Мы можем добавлять элементы в начало или конец нашего Stream с помощью следующих удобных операций:

List<String> appendedUsers = StreamEx.of(users)
.map(User::getName)
.prepend("(none)")
.append("LAST")
.toList();

Мы можем удалить ненужные нулевые элементы с помощью nonNull() и использовать Stream как Iterable :

for (String line : StreamEx.of(users).map(User::getName).nonNull()) {
System.out.println(line);
}

5. Поддержка математических операций и примитивных типов

StreamEx добавляет поддержку примитивных типов, как мы можем видеть в этом самоочевидном примере:

short[] src = {1,2,3};
char[] output = IntStreamEx.of(src)
.map(x -> x * 5)
.toCharArray();

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

Мы можем использовать метод pairMap для выполнения этой операции:

public double[] getDiffBetweenPairs(double... numbers) {
return DoubleStreamEx.of(numbers)
.pairMap((a, b) -> b - a)
.toArray();
}

6. Операции с картой

6.1. Фильтрация по ключам

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

В этом случае мы берем все ненулевые значения:

Map<String, Role> nameToRole = new HashMap<>();
nameToRole.put("first", new Role());
nameToRole.put("second", null);
Set<String> nonNullRoles = StreamEx.ofKeys(nameToRole, Objects::nonNull)
.toSet();

6.2. Работа с парами ключ-значение

Мы также можем работать с парами ключ-значение, создав экземпляр EntryStream :

public Map<User, List<Role>> transformMap( 
Map<Role, List<User>> role2users) {
Map<User, List<Role>> users2roles = EntryStream.of(role2users)
.flatMapValues(List::stream)
.invert()
.grouping();
return users2roles;
}

Специальная операция EntryStream.of берет карту и преобразует ее в поток объектов ключ-значение. Затем мы используем операцию flatMapValues , чтобы преобразовать наш список ролей в поток отдельных значений.

Затем мы можем инвертировать пару ключ-значение, сделав класс User ключом, а класс Role значением.

И, наконец, мы можем использовать операцию группировки , чтобы преобразовать нашу карту в инверсию полученной, всего за четыре операции.

6.3. Сопоставление ключ-значение

Мы также можем сопоставлять ключи и значения независимо друг от друга:

Map<String, String> mapToString = EntryStream.of(users2roles)
.mapKeys(String::valueOf)
.mapValues(String::valueOf)
.toMap();

Благодаря этому мы можем быстро преобразовать наши ключи или значения в другой требуемый тип.

7. Файловые операции

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

StreamEx.ofLines(reader)
.remove(String::isEmpty)
.forEach(System.out::println);

Обратите внимание, что мы использовали метод remove() для фильтрации пустых строк.

Обратите внимание, что StreamEx не закроет файл автоматически. Следовательно, мы должны помнить о ручном выполнении операции закрытия как при чтении файла, так и при записи, чтобы избежать ненужных накладных расходов памяти.

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

В этом уроке мы узнали о StreamEx и его различных утилитах. Еще многое предстоит пройти — и здесь у них есть удобная шпаргалка .

Как всегда, полный исходный код доступен на GitHub .