Какие знаешь промежуточные методы в Stream?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Промежуточные методы в Stream API
Промежуточные методы (intermediate operations) — это методы, которые преобразуют Stream, но не вызывают вычисления. Они возвращают новый Stream и позволяют строить цепочки трансформаций. Вычисления происходят только когда вызывается терминальная операция.
Основные промежуточные методы
1. map() — трансформация элементов
Преобразует каждый элемент в другой тип.
// Пример: преобразуем User в UserDTO
List<User> users = Arrays.asList(
new User(1, "John", "john@example.com"),
new User(2, "Jane", "jane@example.com")
);
List<String> userNames = users.stream()
.map(user -> user.getName()) // User → String
.collect(Collectors.toList());
// ["John", "Jane"]
// Пример: User → UserDTO
List<UserDTO> dtos = users.stream()
.map(user -> new UserDTO(user.getId(), user.getName()))
.collect(Collectors.toList());
// Пример: String → Integer (длина строки)
List<Integer> lengths = Arrays.asList("hello", "world", "stream")
.stream()
.map(String::length) // Method reference
.collect(Collectors.toList());
// [5, 5, 6]
2. flatMap() — разворачивание вложенных потоков
Об одного элемента создаёт несколько элементов (разворачивает вложенные структуры).
// Пример: список пользователей с их заказами
List<User> users = Arrays.asList(
new User(1, "John", Arrays.asList("order1", "order2")),
new User(2, "Jane", Arrays.asList("order3"))
);
// Без flatMap — список списков
List<List<String>> allOrdersNested = users.stream()
.map(user -> user.getOrders())
.collect(Collectors.toList());
// [["order1", "order2"], ["order3"]]
// С flatMap — одномерный список
List<String> allOrders = users.stream()
.flatMap(user -> user.getOrders().stream()) // List<Order> → Stream<Order>
.collect(Collectors.toList());
// ["order1", "order2", "order3"]
// Другой пример: разворачиваем массивы
List<int[]> arrays = Arrays.asList(
new int[]{1, 2, 3},
new int[]{4, 5},
new int[]{6, 7, 8}
);
List<Integer> flattened = arrays.stream()
.flatMap(arr -> Arrays.stream(arr).boxed()) // int[] → Stream<Integer>
.collect(Collectors.toList());
// [1, 2, 3, 4, 5, 6, 7, 8]
3. filter() — фильтрация элементов
Оставляет только элементы, соответствующие условию.
// Пример: отфильтровать активных пользователей
List<User> users = Arrays.asList(
new User(1, "John", true),
new User(2, "Jane", false),
new User(3, "Bob", true)
);
List<User> activeUsers = users.stream()
.filter(user -> user.isActive()) // Condition
.collect(Collectors.toList());
// [User(1, "John"), User(3, "Bob")]
// Пример: числа больше 10
List<Integer> numbers = Arrays.asList(5, 15, 3, 25, 10);
List<Integer> largeNumbers = numbers.stream()
.filter(n -> n > 10)
.collect(Collectors.toList());
// [15, 25]
// Пример: комбинирование фильтров
List<User> filtered = users.stream()
.filter(user -> user.isActive())
.filter(user -> user.getName().startsWith("J"))
.collect(Collectors.toList());
4. distinct() — удаление дубликатов
Оставляет только уникальные элементы.
// Пример: удалить повторяющиеся числа
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 1, 4, 5, 4);
List<Integer> unique = numbers.stream()
.distinct()
.collect(Collectors.toList());
// [1, 2, 3, 4, 5]
// Пример: уникальные имена (работает если переопределён equals/hashCode)
List<User> users = Arrays.asList(
new User(1, "John"),
new User(2, "John"),
new User(3, "Jane")
);
List<User> uniqueUsers = users.stream()
.distinct() // Сравнивает по equals()
.collect(Collectors.toList());
// Если нужны уникальные по одному полю:
List<String> uniqueNames = users.stream()
.map(User::getName)
.distinct()
.collect(Collectors.toList());
// ["John", "Jane"]
5. sorted() — сортировка
Сортирует элементы (натурально или с Comparator).
// Пример: натуральная сортировка
List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5, 9, 2, 6);
List<Integer> sorted = numbers.stream()
.sorted() // По возрастанию
.collect(Collectors.toList());
// [1, 1, 2, 3, 4, 5, 6, 9]
List<Integer> reverseSorted = numbers.stream()
.sorted(Collections.reverseOrder()) // По убыванию
.collect(Collectors.toList());
// [9, 6, 5, 4, 3, 2, 1, 1]
// Пример: сортировка объектов
List<User> users = Arrays.asList(
new User(3, "Charlie"),
new User(1, "Alice"),
new User(2, "Bob")
);
List<User> sortedByName = users.stream()
.sorted(Comparator.comparing(User::getName)) // По имени
.collect(Collectors.toList());
// [Alice, Bob, Charlie]
List<User> sortedById = users.stream()
.sorted(Comparator.comparingInt(User::getId)) // По ID
.collect(Collectors.toList());
// [1, 2, 3]
// Сортировка по нескольким критериям
List<User> multiSort = users.stream()
.sorted(Comparator
.comparingInt(User::getDepartmentId) // Сначала по department
.thenComparing(User::getName)) // Потом по имени
.collect(Collectors.toList());
6. peek() — инспекция элементов (для отладки)
Проходит по элементам и позволяет выполнить действие (обычно для логирования).
// Пример: логирование при фильтрации
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> result = numbers.stream()
.peek(n -> System.out.println("Processing: " + n))
.filter(n -> n > 2)
.peek(n -> System.out.println("After filter: " + n))
.map(n -> n * 2)
.peek(n -> System.out.println("After map: " + n))
.collect(Collectors.toList());
// Логи:
// Processing: 1
// Processing: 2
// Processing: 3
// After filter: 3
// After map: 6
// ...
// Типичный use case: отладка chain'ов
List<User> filtered = users.stream()
.filter(user -> user.isActive())
.peek(user -> logger.debug("Active user: {}", user.getName()))
.filter(user -> user.getAge() > 18)
.peek(user -> logger.debug("Adult user: {}", user.getName()))
.collect(Collectors.toList());
7. limit() — ограничение количества элементов
Оставляет только первые N элементов.
// Пример: получить первые 3 элемента
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> first3 = numbers.stream()
.limit(3)
.collect(Collectors.toList());
// [1, 2, 3]
// Пример: пагинация (первые 10 активных пользователей)
List<User> topActiveUsers = users.stream()
.filter(User::isActive)
.limit(10)
.collect(Collectors.toList());
// Пример: комбинация с sorted
List<String> topStrings = strings.stream()
.sorted(Comparator.comparingInt(String::length).reversed())
.limit(5)
.collect(Collectors.toList());
8. skip() — пропуск элементов
Пропускает первые N элементов.
// Пример: пропустить первые 2 элемента
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> skipped = numbers.stream()
.skip(2)
.collect(Collectors.toList());
// [3, 4, 5]
// Пример: пагинация (skip для offset)
int pageSize = 10;
int pageNumber = 2; // вторая страница (0-indexed)
List<User> pagedUsers = users.stream()
.skip((long) pageNumber * pageSize) // пропускаем offset элементов
.limit(pageSize) // берём limit элементов
.collect(Collectors.toList());
9. dropWhile() и takeWhile() (Java 9+)
Отбрасывает/берёт элементы пока условие верно.
// takeWhile: берём элементы пока условие верно
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 3, 2, 1);
List<Integer> taken = numbers.stream()
.takeWhile(n -> n < 5) // Берём: 1, 2, 3, 4
.collect(Collectors.toList());
// [1, 2, 3, 4] (остановился на 5)
// dropWhile: пропускаем элементы пока условие верно
List<Integer> dropped = numbers.stream()
.dropWhile(n -> n < 5) // Пропускаем: 1, 2, 3, 4
.collect(Collectors.toList());
// [5, 6, 3, 2, 1]
10. mapToInt(), mapToLong(), mapToDouble() — специализированные map'ы
Оптимизированные для примитивных типов.
// Пример: преобразование в IntStream
List<String> strings = Arrays.asList("hello", "world", "stream");
int totalLength = strings.stream()
.mapToInt(String::length) // Stream<String> → IntStream
.sum(); // Терминальная операция
// 14 (5 + 5 + 4)
int maxLength = strings.stream()
.mapToInt(String::length)
.max()
.orElse(0);
// 5
double average = strings.stream()
.mapToInt(String::length)
.average()
.orElse(0);
// 4.666...
// Пример: LongStream для больших чисел
List<Long> bigNumbers = Arrays.asList(1000000L, 2000000L, 3000000L);
long sum = bigNumbers.stream()
.mapToLong(Long::longValue)
.sum();
// 6000000L
Реальный пример: комплексный chain
// Задача: найти 5 самых длинных имён активных пользователей,
// отсортированные по алфавиту
List<String> result = users.stream()
.filter(user -> user.isActive()) // Промежуточная: фильтруем
.map(User::getName) // Промежуточная: преобразуем в имена
.distinct() // Промежуточная: удаляем дубликаты
.sorted(Comparator.comparingInt(String::length).reversed()) // Промежуточная: сортируем по длине
.limit(5) // Промежуточная: берём 5
.sorted() // Промежуточная: сортируем по алфавиту
.peek(name -> System.out.println("Result: " + name)) // Промежуточная: логируем
.collect(Collectors.toList()); // Терминальная: собираем результат
Важные особенности промежуточных методов
-
Ленивость (Laziness)
- Промежуточные методы не вычисляются сразу
- Вычисление происходит только при терминальной операции
-
Цепочка (Chaining)
- Методы возвращают Stream, позволяя строить цепочки
-
Неизменяемость (Immutability)
- Оригинальная коллекция не меняется
- Создаются новые потоки
-
Производительность
- Stream может быть параллельным
- Отодвигает вычисления к концу цепочки
Итоговая таблица
| Метод | Вход | Выход | Описание |
|---|---|---|---|
| map() | Stream<T> | Stream<R> | Трансформирует элементы |
| flatMap() | Stream<T> | Stream<R> | Разворачивает вложенные потоки |
| filter() | Stream<T> | Stream<T> | Оставляет элементы по условию |
| distinct() | Stream<T> | Stream<T> | Удаляет дубликаты |
| sorted() | Stream<T> | Stream<T> | Сортирует элементы |
| peek() | Stream<T> | Stream<T> | Инспектирует (отладка) |
| limit() | Stream<T> | Stream<T> | Берёт первые N |
| skip() | Stream<T> | Stream<T> | Пропускает первые N |
| dropWhile() | Stream<T> | Stream<T> | Пропускает пока условие верно |
| takeWhile() | Stream<T> | Stream<T> | Берёт пока условие верно |
Промежуточные методы — это основа Stream API. Они позволяют писать выразительный и функциональный код.