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

Можно ли реализовать свою промежуточную операцию?

1.0 Junior🔥 131 комментариев
#Основы Java

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

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

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

Да, можно реализовать свою промежуточную операцию

Промежуточные операции (intermediate operations) в Stream API можно создавать самостоятельно, реализуя нужный функциональный интерфейс или создавая расширение интерфейса Stream.

Что такое промежуточная операция

Промежуточные операции — это методы, которые:

  • Принимают Stream<T> и возвращают Stream<R>
  • Ленивые (lazy) — не выполняются сразу
  • Могут быть stateless (filter, map) или stateful (sorted, distinct)
  • Примеры: filter(), map(), flatMap(), sorted(), distinct()

Способ 1: Создание метода-утилиты

import java.util.stream.Stream;
import java.util.function.Function;

public class StreamUtils {
    /**
     * Промежуточная операция: фильтр по длине строки
     */
    public static Stream<String> filterByLength(Stream<String> stream, int minLength) {
        return stream.filter(s -> s.length() >= minLength);
    }
    
    /**
     * Промежуточная операция: преобразование в верхний регистр
     */
    public static Stream<String> toUpperCase(Stream<String> stream) {
        return stream.map(String::toUpperCase);
    }
    
    /**
     * Промежуточная операция: повторяем каждый элемент N раз
     */
    public static <T> Stream<T> repeat(Stream<T> stream, int times) {
        return stream.flatMap(item -> 
            Stream.generate(() -> item).limit(times)
        );
    }
    
    public static void main(String[] args) {
        var words = Stream.of("Java", "Stream", "API", "Cool");
        
        var result = StreamUtils.filterByLength(words, 4)
            .collect(java.util.stream.Collectors.toList());
        
        System.out.println(result);  // [Stream, Cool]
    }
}

Способ 2: Использование Function для цепочки

import java.util.stream.Stream;
import java.util.function.Function;

public class CustomStreamOperations {
    /**
     * Фабрика для создания кастомных промежуточных операций
     */
    public static <T> Function<Stream<T>, Stream<T>> debounce() {
        return stream -> stream.filter(item -> item != null);
    }
    
    public static <T> Function<Stream<T>, Stream<T>> skip(int count) {
        return stream -> stream.skip(count);
    }
    
    public static <T, R> Function<Stream<T>, Stream<R>> mapCustom(
            Function<T, R> mapper) {
        return stream -> stream.map(mapper);
    }
    
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("a", null, "b", "c", "d");
        
        // Цепочка кастомных операций
        var result = stream
            .filter(s -> s != null)  // фильтр NULL
            .skip(1)                 // пропустить 1
            .map(String::toUpperCase) // в верхний регистр
            .limit(2)                // первые 2
            .collect(java.util.stream.Collectors.toList());
        
        System.out.println(result);  // [B, C]
    }
}

Способ 3: Интерсепторы с Function

import java.util.stream.Stream;
import java.util.function.Function;
import java.util.function.Predicate;

public class StreamChain<T> {
    private Stream<T> stream;
    
    public StreamChain(Stream<T> stream) {
        this.stream = stream;
    }
    
    // Кастомная промежуточная операция 1
    public StreamChain<T> filterEven(Predicate<T> predicate) {
        this.stream = stream.filter(predicate);
        return this;
    }
    
    // Кастомная промежуточная операция 2
    public <R> StreamChain<R> mapCustom(Function<T, R> mapper) {
        this.stream = (Stream<T>) stream.map(mapper);
        return (StreamChain<R>) this;
    }
    
    // Кастомная промежуточная операция 3: логирование
    public StreamChain<T> peek(String label) {
        this.stream = stream.peek(item -> 
            System.out.println(label + ": " + item)
        );
        return this;
    }
    
    public Stream<T> build() {
        return stream;
    }
    
    public static void main(String[] args) {
        var result = new StreamChain<>(Stream.of(1, 2, 3, 4, 5))
            .peek("Начало")
            .filterEven(x -> x % 2 == 0)
            .peek("После фильтра")
            .build()
            .collect(java.util.stream.Collectors.toList());
        
        System.out.println("Результат: " + result);
    }
}

Способ 4: Fluent Builder для операций

import java.util.stream.Stream;
import java.util.function.Function;
import java.util.function.Predicate;

public class FluentStreamBuilder<T> {
    private final Stream<T> stream;
    
    public FluentStreamBuilder(Stream<T> stream) {
        this.stream = stream;
    }
    
    /**
     * Кастомная промежуточная: повторение элементов
     */
    public Stream<T> repeatEach(int times) {
        return stream.flatMap(item -> 
            Stream.generate(() -> item).limit(times)
        );
    }
    
    /**
     * Кастомная промежуточная: интерлив двух потоков
     */
    public Stream<T> interleave(Stream<T> other) {
        return Stream.concat(
            stream.flatMap(item -> Stream.of(item)),
            other
        );
    }
    
    /**
     * Кастомная промежуточная: окна (sliding window)
     */
    public Stream<java.util.List<T>> sliding(int windowSize) {
        return stream
            .collect(java.util.stream.Collectors.toList())
            .stream()
            .flatMap(list -> 
                java.util.stream.IntStream.range(0, list.size() - windowSize + 1)
                    .mapToObj(i -> list.subList(i, i + windowSize))
            );
    }
    
    public Stream<T> get() {
        return stream;
    }
}

public class Example {
    public static void main(String[] args) {
        var nums = java.util.Arrays.asList(1, 2, 3);
        
        var result = new FluentStreamBuilder<>(nums.stream())
            .repeatEach(2)
            .collect(java.util.stream.Collectors.toList());
        
        System.out.println(result);  // [1, 1, 2, 2, 3, 3]
    }
}

Способ 5: Расширение Stream через конкретный класс

import java.util.stream.Stream;

public class EnhancedStream<T> {
    private Stream<T> delegate;
    
    public EnhancedStream(Stream<T> stream) {
        this.delegate = stream;
    }
    
    /**
     * Кастомная промежуточная операция: батчинг
     */
    public Stream<java.util.List<T>> batch(int size) {
        return delegate
            .collect(java.util.stream.Collectors.toList())
            .stream()
            .flatMap(list -> 
                java.util.stream.IntStream.range(0, (list.size() + size - 1) / size)
                    .mapToObj(i -> list.subList(
                        i * size,
                        Math.min((i + 1) * size, list.size())
                    ))
            );
    }
    
    /**
     * Кастомная промежуточная операция: фильтр по индексу
     */
    public Stream<T> filterByIndex(java.util.function.IntPredicate indexPredicate) {
        return delegate
            .collect(java.util.stream.Collectors.toList())
            .stream()
            .filter(item -> {
                int index = delegate
                    .collect(java.util.stream.Collectors.toList())
                    .indexOf(item);
                return indexPredicate.test(index);
            });
    }
    
    public Stream<T> getStream() {
        return delegate;
    }
    
    public static void main(String[] args) {
        var nums = java.util.Arrays.asList(1, 2, 3, 4, 5, 6);
        
        var batches = new EnhancedStream<>(nums.stream())
            .batch(2)
            .collect(java.util.stream.Collectors.toList());
        
        System.out.println(batches);  // [[1, 2], [3, 4], [5, 6]]
    }
}

Важные принципы

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

Stream<String> stream = Stream.of("a", "b", "c")
    .map(s -> {  // Это НЕ выполнится сейчас!
        System.out.println("Обработка: " + s);
        return s.toUpperCase();
    });

// Выполнится только здесь (терминальная операция)
stream.forEach(System.out::println);

Stateless vs Stateful:

// Stateless (не требует состояния)
stream.filter(x -> x > 5)    // каждый элемент независим
stream.map(x -> x * 2)       // каждый элемент независим

// Stateful (требует буферизации)
stream.sorted()              // нужно знать все элементы
stream.distinct()            // нужно помнить виденные элементы
stream.limit(5)              // нужен счётчик

Лучшие практики

  1. Сохраняй ленивость — операции должны выполняться при терминальной операции
  2. Избегай побочных эффектов — используй peek() только для отладки
  3. Документируй является ли операция stateful или stateless
  4. Тестируй производительность — некоторые комбинации неэффективны

Заключение

Пользовательские промежуточные операции можно создавать:

  • Методы-утилиты, возвращающие Stream<T>
  • Function<Stream<T>, Stream<R>> функции
  • Fluent builders для читаемости
  • Расширения Stream через обёрнутые классы

Главное — сохранять ленивость вычисления и избегать побочных эффектов.

Можно ли реализовать свою промежуточную операцию? | PrepBro