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

Какие знаешь компоненты в Stream API?

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

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

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

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

Компоненты Java Stream API

Stream API — это функциональный подход к обработке коллекций данных в Java. Появился в Java 8 и позволяет писать декларативный, композируемый код для работы с последовательностями элементов.

Архитектура Stream API

Stream обработка состоит из трёх основных компонентов:

Источник данных → Промежуточные операции → Терминальная операция → Результат

1. Источники данных (Sources)

Место, откуда берутся данные для обработки.

import java.util.*;
import java.util.stream.*;

public class StreamSourcesExample {
    
    // Коллекции
    public static void fromCollection() {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        Stream<Integer> stream = numbers.stream();
    }
    
    // Массивы
    public static void fromArray() {
        int[] numbers = {1, 2, 3, 4, 5};
        IntStream stream = Arrays.stream(numbers);
    }
    
    // Строки
    public static void fromString() {
        String text = "Hello World";
        IntStream stream = text.chars();  // Stream символов
    }
    
    // Диапазоны
    public static void fromRange() {
        IntStream range1 = IntStream.range(0, 10);        // [0, 10)
        IntStream range2 = IntStream.rangeClosed(0, 10);  // [0, 10]
    }
    
    // Бесконечные потоки
    public static void infinite() {
        Stream<Integer> infinite1 = Stream.generate(() -> 42);
        Stream<Integer> infinite2 = Stream.iterate(0, n -> n + 1);
    }
    
    // Files и IO
    public static void fromFile() throws java.io.IOException {
        Stream<String> lines = java.nio.file.Files
            .lines(java.nio.file.Paths.get("file.txt"));
    }
    
    // Пользовательские источники
    public static void custom() {
        Stream<String> stream = Stream.of("a", "b", "c");
    }
}

Типы Stream

Типизированные примитивные потоки

public class PrimitiveStreams {
    
    public void demonstratePrimitiveStreams() {
        // IntStream
        IntStream intStream = IntStream.range(0, 100);
        int sum = intStream.sum();
        double average = intStream.average().orElse(0);
        
        // LongStream
        LongStream longStream = LongStream.rangeClosed(1, 1_000_000);
        long product = longStream.reduce(1, (a, b) -> a * b);
        
        // DoubleStream
        DoubleStream doubleStream = DoubleStream.of(1.5, 2.5, 3.5);
        double sum2 = doubleStream.sum();
    }
    
    // Коробление и разворачивание
    public void boxing() {
        IntStream intStream = IntStream.range(0, 10);
        
        // Из IntStream в Stream<Integer>
        Stream<Integer> boxed = intStream.boxed();
        
        // Обратно
        Stream<Integer> integers = Stream.of(1, 2, 3);
        IntStream unboxed = integers.mapToInt(i -> i);
    }
}

Промежуточные операции (Intermediate Operations)

Промежуточные операции преобразуют stream, возвращая новый stream. Они ленивые — не выполняются до вызова терминальной операции.

filter() — фильтрация

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

List<Integer> evenNumbers = numbers.stream()
    .filter(n -> n % 2 == 0)  // Оставляет только чётные
    .collect(Collectors.toList());

// Результат: [2, 4, 6]

map() — преобразование

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

List<Integer> lengths = names.stream()
    .map(String::length)  // Преобразует в длину
    .collect(Collectors.toList());

// Результат: [5, 3, 7]

flatMap() — развёртывание

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

List<Integer> flattened = nestedLists.stream()
    .flatMap(List::stream)  // Развёртывает вложенные списки
    .collect(Collectors.toList());

// Результат: [1, 2, 3, 4, 5, 6]

distinct() — уникальные элементы

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]

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

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

// Естественный порядок
List<Integer> sorted = numbers.stream()
    .sorted()  // Использует compareTo
    .collect(Collectors.toList());

// Пользовательский порядок
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
List<String> byLength = names.stream()
    .sorted(Comparator.comparingInt(String::length))
    .collect(Collectors.toList());

// Результат: ["Bob", "Alice", "Charlie"]

peek() — отладочный просмотр

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

List<Integer> result = numbers.stream()
    .peek(n -> System.out.println("Before: " + n))
    .map(n -> n * 2)
    .peek(n -> System.out.println("After: " + n))
    .collect(Collectors.toList());

// Выведет:
// Before: 1
// After: 2
// Before: 2
// After: 4
// Before: 3
// After: 6

limit() и skip()

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

// Первые 3 элемента
List<Integer> first3 = numbers.stream()
    .limit(3)
    .collect(Collectors.toList());  // [1, 2, 3]

// Пропустить первые 2, взять следующие 3
List<Integer> middle = numbers.stream()
    .skip(2)
    .limit(3)
    .collect(Collectors.toList());  // [3, 4, 5]

Терминальные операции (Terminal Operations)

Терминальные операции завершают stream и возвращают результат. После терминальной операции stream больше нельзя использовать.

collect() — сбор результатов

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

// В список
List<Integer> list = numbers.stream()
    .collect(Collectors.toList());

// В set
Set<Integer> set = numbers.stream()
    .collect(Collectors.toSet());

// В map
Map<Integer, String> map = numbers.stream()
    .collect(Collectors.toMap(
        n -> n,
        n -> "number" + n
    ));

// В строку
String joined = numbers.stream()
    .map(String::valueOf)
    .collect(Collectors.joining(", "));  // "1, 2, 3, 4, 5"

// Группировка
Map<Boolean, List<Integer>> byEvenOdd = numbers.stream()
    .collect(Collectors.partitioningBy(n -> n % 2 == 0));
    // {true: [2, 4], false: [1, 3, 5]}

reduce() — свёртка

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

// С начальным значением
int sum = numbers.stream()
    .reduce(0, (a, b) -> a + b);  // 15

int product = numbers.stream()
    .reduce(1, (a, b) -> a * b);  // 120

// Без начального значения (возвращает Optional)
Optional<Integer> maxOpt = numbers.stream()
    .reduce((a, b) -> a > b ? a : b);

if (maxOpt.isPresent()) {
    System.out.println("Max: " + maxOpt.get());  // 5
}

forEach() — итерация

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

names.stream()
    .forEach(System.out::println);

// Sequential
names.stream()
    .forEach(name -> System.out.println("Name: " + name));

// Parallel (параллельная обработка)
names.parallelStream()
    .forEach(System.out::println);

match() операции

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

// Все ли элементы соответствуют условию?
boolean allPositive = numbers.stream()
    .allMatch(n -> n > 0);  // true

// Хотя бы один соответствует?
boolean hasEven = numbers.stream()
    .anyMatch(n -> n % 2 == 0);  // true

// Ни один не соответствует?
boolean noneNegative = numbers.stream()
    .noneMatch(n -> n < 0);  // true

find() операции

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

// Первый элемент (в порядке обхода)
Optional<Integer> first = numbers.stream()
    .filter(n -> n > 2)
    .findFirst();  // Optional[3]

// Любой элемент (может отличаться в параллельных потоках)
Optional<Integer> any = numbers.stream()
    .filter(n -> n > 2)
    .findAny();  // Optional[3] или другой > 2

count() и aggregates

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

long count = numbers.stream()
    .filter(n -> n % 2 == 0)
    .count();  // 2

// IntStream специальные методы
int sum = IntStream.range(0, 10)
    .sum();  // 45

Optional<Integer> max = numbers.stream()
    .max(Comparator.naturalOrder());  // Optional[5]

Optional<Integer> min = numbers.stream()
    .min(Comparator.naturalOrder());  // Optional[1]

Ленивость Stream

public class LazyStreamExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        
        // Эта цепь НИ ЧТО НЕ ВЫПОЛНЯЕТ (ленивая)
        Stream<Integer> stream = numbers.stream()
            .filter(n -> {
                System.out.println("Filtering: " + n);
                return n > 2;
            })
            .map(n -> {
                System.out.println("Mapping: " + n);
                return n * 2;
            });
        
        System.out.println("Stream создан, но ничего не выполнено!");
        
        // Только здесь начинает выполняться обработка
        List<Integer> result = stream.collect(Collectors.toList());
        System.out.println("Result: " + result);
    }
}

Параллельные потоки

public class ParallelStreamExample {
    public static void main(String[] args) {
        List<Integer> numbers = IntStream.range(0, 1_000_000)
            .boxed()
            .collect(Collectors.toList());
        
        // Последовательный поток
        long startSeq = System.nanoTime();
        int sumSeq = numbers.stream()
            .filter(n -> n % 2 == 0)
            .mapToInt(n -> n)
            .sum();
        long timeSeq = System.nanoTime() - startSeq;
        
        // Параллельный поток
        long startPar = System.nanoTime();
        int sumPar = numbers.parallelStream()
            .filter(n -> n % 2 == 0)
            .mapToInt(n -> n)
            .sum();
        long timePar = System.nanoTime() - startPar;
        
        System.out.println("Sequential: " + timeSeq);
        System.out.println("Parallel: " + timePar);
    }
}

Практические рекомендации

  1. Используй stream для трансформаций данных, а не для побочных эффектов
  2. Избегай peek() в production коде — это для отладки
  3. Параллельные потоки для больших наборов (>1000 элементов)
  4. Помни о ленивости — результат не вычисляется до терминальной операции
  5. Не переиспользуй stream — нельзя применить две терминальные операции
  6. Используй специализированные потоки (IntStream, LongStream) для примитивов
  7. Kombinuj операции для читаемости — не создавай слишком длинные цепи

Stream API делает код более функциональным, читаемым и часто более эффективным благодаря ленивой оценке и оптимизациям компилятора.