«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
}

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

public class Role {
}

3. Методы быстрого доступа для коллекционеров

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

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

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

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

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

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

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

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"

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

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

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

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

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

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

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

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.