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

Приведи пример промежуточных операций в Stream API

2.0 Middle🔥 101 комментариев
#Stream API и функциональное программирование

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

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

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

Промежуточные операции в Stream API

Stream API в Java предоставляет функциональный подход к обработке коллекций. Промежуточные операции (Intermediate Operations) — это операции, которые возвращают новый Stream и позволяют строить цепочки преобразований. Они выполняются ленивым образом (lazy) — только при вызове терминальной операции.

1. filter() — фильтрация элементов

Отбирает элементы, соответствующие условию:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

// Оставляем только четные числа
List<Integer> evenNumbers = numbers.stream()
    .filter(n -> n % 2 == 0)
    .collect(Collectors.toList());
// Результат: [2, 4, 6, 8, 10]

// Более сложная фильтрация
List<String> words = Arrays.asList("java", "stream", "api", "java");
List<String> filtered = words.stream()
    .filter(w -> w.length() > 3)
    .filter(w -> !w.equals("java"))  // Можно цеплять несколько filter
    .collect(Collectors.toList());
// Результат: ["stream"]

2. map() — преобразование элементов

Преобразует каждый элемент в новый элемент:

List<String> words = Arrays.asList("hello", "world");

// Преобразуем строки в их длину
List<Integer> lengths = words.stream()
    .map(String::length)
    .collect(Collectors.toList());
// Результат: [5, 5]

// Преобразование объектов
List<User> users = Arrays.asList(
    new User(1, "Alice"),
    new User(2, "Bob")
);

List<String> userNames = users.stream()
    .map(User::getName)
    .collect(Collectors.toList());
// Результат: ["Alice", "Bob"]

// Преобразование в объект другого типа
List<UserDto> dtos = users.stream()
    .map(user -> new UserDto(user.getId(), user.getName().toUpperCase()))
    .collect(Collectors.toList());

3. flatMap() — развертывание (flatten) потоков

Преобразует каждый элемент в Stream и объединяет все потоки в один:

List<String> sentences = Arrays.asList(
    "hello world",
    "java stream",
    "api test"
);

// Развертываем предложения в отдельные слова
List<String> words = sentences.stream()
    .flatMap(sentence -> Arrays.stream(sentence.split(" ")))
    .collect(Collectors.toList());
// Результат: ["hello", "world", "java", "stream", "api", "test"]

// Более сложный пример с коллекциями
List<List<Integer>> numbers = Arrays.asList(
    Arrays.asList(1, 2),
    Arrays.asList(3, 4),
    Arrays.asList(5, 6)
);

List<Integer> flattened = numbers.stream()
    .flatMap(List::stream)
    .collect(Collectors.toList());
// Результат: [1, 2, 3, 4, 5, 6]

// Фильтрация и развертывание
List<User> users = Arrays.asList(
    new User(1, "Alice", Arrays.asList("java", "python")),
    new User(2, "Bob", Arrays.asList("java", "go"))
);

List<String> skills = users.stream()
    .flatMap(user -> user.getSkills().stream())
    .distinct()
    .collect(Collectors.toList());
// Результат: ["java", "python", "go"]

4. distinct() — удаление дубликатов

Если элементы наследуют Object, используется equals/hashCode:

List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 3, 4);

List<Integer> unique = numbers.stream()
    .distinct()
    .collect(Collectors.toList());
// Результат: [1, 2, 3, 4]

// С объектами (нужны корректные equals/hashCode)
List<User> users = Arrays.asList(
    new User(1, "Alice"),
    new User(1, "Alice"),  // Дубликат
    new User(2, "Bob")
);

List<User> uniqueUsers = users.stream()
    .distinct()
    .collect(Collectors.toList());
// Результат: [User(1, Alice), User(2, Bob)]

5. sorted() — сортировка

Сортирует элементы в натуральном порядке или по компаратору:

List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 9, 3);

// Сортировка в натуральном порядке
List<Integer> sorted = numbers.stream()
    .sorted()
    .collect(Collectors.toList());
// Результат: [1, 2, 3, 5, 8, 9]

// Сортировка в обратном порядке
List<Integer> descending = numbers.stream()
    .sorted(Comparator.reverseOrder())
    .collect(Collectors.toList());
// Результат: [9, 8, 5, 3, 2, 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> sorted = users.stream()
    .sorted(Comparator.comparing(User::getAge)
        .thenComparing(User::getName))
    .collect(Collectors.toList());

6. peek() — инспекция без изменения

Выполняет действие для каждого элемента без изменения потока (полезно для отладки):

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

List<Integer> result = numbers.stream()
    .filter(n -> n % 2 == 0)
    .peek(n -> System.out.println("Найдено четное число: " + n))
    .map(n -> n * n)
    .peek(n -> System.out.println("Результат квадрата: " + n))
    .collect(Collectors.toList());
// Печать:
// Найдено четное число: 2
// Результат квадрата: 4
// Найдено четное число: 4
// Результат квадрата: 16
// Результат: [4, 16]

// Практический пример логирования
List<User> processed = users.stream()
    .peek(user -> logger.info("Processing user: {}", user.getName()))
    .filter(user -> user.isActive())
    .peek(user -> logger.debug("User {} is active", user.getName()))
    .collect(Collectors.toList());

7. limit() и skip() — ограничение и пропуск

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

// Первые 5 элементов
List<Integer> first5 = numbers.stream()
    .limit(5)
    .collect(Collectors.toList());
// Результат: [1, 2, 3, 4, 5]

// Пропустить первые 3 элемента
List<Integer> skipped = numbers.stream()
    .skip(3)
    .collect(Collectors.toList());
// Результат: [4, 5, 6, 7, 8, 9, 10]

// Пагинация: элементы 6-10
List<Integer> page2 = numbers.stream()
    .skip(5)        // Пропустить первую страницу (размер 5)
    .limit(5)       // Взять 5 элементов
    .collect(Collectors.toList());
// Результат: [6, 7, 8, 9, 10]

Цепочка промежуточных операций

List<User> users = Arrays.asList(
    new User(1, "Alice", 25),
    new User(2, "Bob", 30),
    new User(3, "Charlie", 22),
    new User(4, "Diana", 28)
);

// Сложная цепочка преобразований
List<String> result = users.stream()
    .filter(user -> user.getAge() >= 25)           // Фильтруем по возрасту
    .sorted(Comparator.comparing(User::getName))  // Сортируем по имени
    .map(User::getName)                           // Преобразуем в имена
    .map(String::toUpperCase)                      // В заглавные буквы
    .limit(2)                                     // Берем первых двух
    .peek(name -> System.out.println("Name: " + name))  // Логируем
    .collect(Collectors.toList());
// Печать:
// Name: ALICE
// Name: DIANA
// Результат: ["ALICE", "DIANA"]

Важные особенности

  • Ленивость (Laziness): промежуточные операции не выполняются до вызова терминальной операции
  • Цепочка методов: позволяет писать выразительный, декларативный код
  • Неизменяемость: исходная коллекция не изменяется
  • Производительность: Stream API оптимизирует вычисления, пропуская ненужные операции

Промежуточные операции — основа функционального программирования в Java, позволяя писать чистый и понятный код для обработки коллекций.

Приведи пример промежуточных операций в Stream API | PrepBro