← Назад к вопросам

Какие знаешь промежуточные методы в Stream?

1.6 Junior🔥 251 комментариев
#Stream API и функциональное программирование

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Промежуточные методы в 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()); // Терминальная: собираем результат

Важные особенности промежуточных методов

  1. Ленивость (Laziness)

    • Промежуточные методы не вычисляются сразу
    • Вычисление происходит только при терминальной операции
  2. Цепочка (Chaining)

    • Методы возвращают Stream, позволяя строить цепочки
  3. Неизменяемость (Immutability)

    • Оригинальная коллекция не меняется
    • Создаются новые потоки
  4. Производительность

    • 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. Они позволяют писать выразительный и функциональный код.