← Назад к вопросам
Можно ли реализовать свою промежуточную операцию?
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) // нужен счётчик
Лучшие практики
- Сохраняй ленивость — операции должны выполняться при терминальной операции
- Избегай побочных эффектов — используй peek() только для отладки
- Документируй является ли операция stateful или stateless
- Тестируй производительность — некоторые комбинации неэффективны
Заключение
Пользовательские промежуточные операции можно создавать:
- Методы-утилиты, возвращающие Stream<T>
- Function<Stream<T>, Stream<R>> функции
- Fluent builders для читаемости
- Расширения Stream через обёрнутые классы
Главное — сохранять ленивость вычисления и избегать побочных эффектов.