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