3.2 操作符链:map、filter、take

从零手写三个核心操作符——MapOperator、FilterOperator、TakeOperator——说明「操作符返回新 Publisher」的装饰器模式、request 信号的累减与转发、cancel 向上游的传播链,以及 take(N) 在达到数量后主动 cancel 上游的实现细节。每段代码都配以运行输出与边界测试。

操作符的本质

在 Reactor 和 RxJava 中,操作符不是「函数调用」,而是返回新 Publisher 的装饰器。当你写:

Flux.just(1, 2, 3)
    .map(i -> i * 2)
    .filter(i -> i > 2)
    .take(2);

实际上发生了:

TakePublisher(
    FilterPublisher(
        MapPublisher(
            JustPublisher(1, 2, 3)
        )
    )
)

每个操作符创建一个新的 Publisher,它包装了上游 Publisher,拦截并转换信号后再转发给下游。

共同模式

所有操作符共享以下结构:

public class XxxOperator<T, R> implements Publisher<R> {
    private final Publisher<T> upstream;
    private final SomeFunction<T, R> function;

    @Override
    public void subscribe(Subscriber<? super R> downstream) {
        upstream.subscribe(new XxxSubscriber(downstream));
    }

    private class XxxSubscriber implements Subscriber<T> {
        private final Subscriber<? super R> downstream;
        private Subscription upstreamSubscription;

        // onSubscribe: 保存上游 Subscription,向下游发送自己的 Subscription
        // onNext: 转换/过滤/计数,再发射给 downstream
        // onError/onComplete: 透传
    }
}

MapOperator

package com.plume.eventd.core;

import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import java.util.function.Function;

public final class MapOperator<T, R> implements Publisher<R> {

    private final Publisher<T> upstream;
    private final Function<T, R> mapper;

    public MapOperator(Publisher<T> upstream, Function<T, R> mapper) {
        this.upstream = upstream;
        this.mapper = mapper;
    }

    @Override
    public void subscribe(Subscriber<? super R> downstream) {
        upstream.subscribe(new MapSubscriber(downstream));
    }

    private class MapSubscriber implements Subscriber<T> {
        private final Subscriber<? super R> downstream;
        private Subscription upstreamSubscription;

        MapSubscriber(Subscriber<? super R> downstream) {
            this.downstream = downstream;
        }

        @Override
        public void onSubscribe(Subscription subscription) {
            this.upstreamSubscription = subscription;
            // 向下游发送一个「透明」的 Subscription
            downstream.onSubscribe(new Subscription() {
                @Override public void request(long n) {
                    upstreamSubscription.request(n);
                }
                @Override public void cancel() {
                    upstreamSubscription.cancel();
                }
            });
        }

        @Override
        public void onNext(T item) {
            try {
                R result = mapper.apply(item);
                downstream.onNext(result);
            } catch (Exception e) {
                upstreamSubscription.cancel();
                downstream.onError(e);
            }
        }

        @Override public void onError(Throwable t) {
            downstream.onError(t);
        }
        @Override public void onComplete() {
            downstream.onComplete();
        }
    }
}

关键洞察:Subscription 的透明转发

MapSubscriber.onSubscribe 创建了一个新的 Subscription 对象发给下游。这个对象只是简单地转发 request 和 cancel 给上游。

为什么不直接把上游的 Subscription 发给下游?因为下游可能调用 cancel(),而 MapSubscriber 需要在 cancel 时做清理(虽然本例没有)。更关键的是,这保持了分层结构——每个操作符都有机会拦截和修改信号。

FilterOperator

package com.plume.eventd.core;

import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import java.util.function.Predicate;

public final class FilterOperator<T> implements Publisher<T> {

    private final Publisher<T> upstream;
    private final Predicate<T> predicate;

    public FilterOperator(Publisher<T> upstream, Predicate<T> predicate) {
        this.upstream = upstream;
        this.predicate = predicate;
    }

    @Override
    public void subscribe(Subscriber<? super T> downstream) {
        upstream.subscribe(new FilterSubscriber(downstream));
    }

    private class FilterSubscriber implements Subscriber<T> {
        private final Subscriber<? super T> downstream;
        private Subscription upstreamSubscription;

        FilterSubscriber(Subscriber<? super T> downstream) {
            this.downstream = downstream;
        }

        @Override
        public void onSubscribe(Subscription subscription) {
            this.upstreamSubscription = subscription;
            downstream.onSubscribe(new Subscription() {
                @Override public void request(long n) {
                    upstreamSubscription.request(n);
                }
                @Override public void cancel() {
                    upstreamSubscription.cancel();
                }
            });
        }

        @Override
        public void onNext(T item) {
            boolean pass;
            try {
                pass = predicate.test(item);
            } catch (Throwable e) {
                upstreamSubscription.cancel();
                downstream.onError(e);
                return;
            }
            if (pass) {
                downstream.onNext(item);
            } else {
                // 被丢弃的元素已经消耗了一份下游需求,必须补回来
                upstreamSubscription.request(1);
            }
        }

        @Override public void onError(Throwable t) {
            downstream.onError(t);
        }
        @Override public void onComplete() {
            downstream.onComplete();
        }
    }
}

关键洞察:过滤必须补偿 request

FilterOperator 把下游的 request(n) 转发给上游,但过滤掉的元素已经消耗了一份额度。如果只转发、不补偿,下游请求 1 个而恰好被过滤时,上游额度归零,整条流停在那里等一个永远不会到来的 request。

所以丢弃元素时要立刻补 request(1):

} else {
    upstreamSubscription.request(1);
}

Reactor 的 FluxFilter 正是这么做的——FilterSubscriber.onNext 在谓词返回 false 时调用 s.request(1)(3.8.8 字节码里可以看到这条调用)。这也是「request(10) 不保证收到 10 个、但过滤器会尽力补足」的实现基础。

即便如此,下游也不能假设「请求 n 必得 n」:上游数据耗尽时会发 onComplete,§1.2 允许少发后终止。业务侧应该以终止信号为准,而不是以请求数量为准。

TakeOperator

package com.plume.eventd.core;

import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;

public final class TakeOperator<T> implements Publisher<T> {

    private final Publisher<T> upstream;
    private final long limit;

    public TakeOperator(Publisher<T> upstream, long limit) {
        this.upstream = upstream;
        this.limit = limit;
    }

    @Override
    public void subscribe(Subscriber<? super T> downstream) {
        upstream.subscribe(new TakeSubscriber(downstream));
    }

    private class TakeSubscriber implements Subscriber<T> {
        private final Subscriber<? super T> downstream;
        private Subscription upstreamSubscription;
        private final AtomicLong remaining = new AtomicLong(limit);
        private final AtomicBoolean completed = new AtomicBoolean(false);

        TakeSubscriber(Subscriber<? super T> downstream) {
            this.downstream = downstream;
        }

        @Override
        public void onSubscribe(Subscription subscription) {
            this.upstreamSubscription = subscription;
            downstream.onSubscribe(new Subscription() {
                @Override public void request(long n) {
                    if (limit <= 0) return;          // take(0):不向上游要任何元素
                    upstreamSubscription.request(n);
                }
                @Override public void cancel() {
                    upstreamSubscription.cancel();
                }
            });
            if (limit <= 0 && completed.compareAndSet(false, true)) {
                subscription.cancel();
                downstream.onComplete();             // 立即完成,不等上游
            }
        }

        @Override
        public void onNext(T item) {
            if (completed.get()) return;
            long rem = remaining.decrementAndGet();
            if (rem >= 0) {
                downstream.onNext(item);
                if (rem == 0) {
                    // 达到数量限制,取消上游并通知下游完成
                    if (completed.compareAndSet(false, true)) {
                        upstreamSubscription.cancel();
                        downstream.onComplete();
                    }
                }
            }
            // rem < 0 意味着已经取消,忽略额外元素
        }

        @Override public void onError(Throwable t) {
            if (completed.compareAndSet(false, true)) {
                downstream.onError(t);
            }
        }
        @Override public void onComplete() {
            if (completed.compareAndSet(false, true)) {
                downstream.onComplete();
            }
        }
    }
}

关键洞察:主动取消上游

TakeOperator 的关键行为是:当发射数量达到限制时,主动取消上游并向下游发出 onComplete。

if (rem == 0) {
    upstreamSubscription.cancel();   // 停止上游不再发射
    downstream.onComplete();          // 告诉下游「完成了」
}

为什么要在 onNext 里发 onComplete?因为 TakeSubscriber 是中间层,它决定了「什么时候算完成」。上游可能还有更多数据,但 Take 说「我只要 N 个」。

take(0) 是一个必须单独处理的边界:下游可能永远等不到 onNext 来触发完成逻辑。正确行为是在 onSubscribe 里直接 cancel 上游并 onComplete,本实现用 limit <= 0 分支做了这件事。Reactor 的 Flux.take(0) 同样在订阅阶段立即完成。

操作符链的完整示例

import com.plume.eventd.core.*;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import java.util.concurrent.Executors;

public class OperatorChainDemo {
    public static void main(String[] args) {
        // 上游:异步发射 1 到 10
        AsyncRangePublisher upstream = new AsyncRangePublisher(
            1, 11, Executors.newSingleThreadExecutor());

        // 操作符链:map(x -> x * 2) -> filter(x > 5) -> take(3)
        TakeOperator<Integer> chain = new TakeOperator<>(
            new FilterOperator<>(
                new MapOperator<>(upstream, x -> x * 2),
                x -> x > 5
            ),
            3
        );

        chain.subscribe(new Subscriber<>() {
            @Override public void onSubscribe(Subscription s) {
                s.request(Long.MAX_VALUE);
            }
            @Override public void onNext(Integer i) {
                System.out.println("result: " + i);
            }
            @Override public void onError(Throwable t) {
                System.out.println("error: " + t.getMessage());
            }
            @Override public void onComplete() {
                System.out.println("done");
            }
        });

        try { Thread.sleep(200); } catch (InterruptedException e) {}
        System.exit(0);
    }
}

计算过程:

上游map(x*2)filter(x>5)take(3)
12丢弃—
24丢弃—
36通过emit 6 (1/3)
48通过emit 8 (2/3)
510通过emit 10 (3/3) → cancel 上游
6..10——已取消,不再接收

输出:

result: 6
result: 8
result: 10
done

操作符链的 request 信号流

操作符链中的 request 是如何传递的?

downstream.subscriber(request 3)
    → TakeOperator: 转发 request(3) 给 FilterOperator
        → FilterOperator: 转发 request(3) 给 MapOperator
            → MapOperator: 转发 request(3) 给 AsyncRangePublisher
                → AsyncRangePublisher: 发射 3 个元素

大多数操作符把 request(n) 原样转发给上游,但有三类会修改额度:

类型request 行为例子
过滤型丢弃元素时补 request(1)filter、distinct
截断型达到上限后 cancel,不再转发take(n)、takeUntil
缓冲/聚合型下游 request 1,上游 request N 或 Long.MAX_VALUEonBackpressureBuffer、collectList

图中的 take(3) 在上游发出第 3 个通过元素后立即 cancel;filter 每丢弃一个元素就向上游补一个请求,否则下游的额度会被「过滤」悄悄吃掉。

操作符的实现检查清单

检查项说明常见错误
subscribe 返回新对象不修改上游,返回包装 Publisher直接返回上游
onSubscribe 发送透明 Subscription让下游可以 request/cancel不发 Subscription
request 转发给上游大多数操作符直接转发request 不转发(下游收不到数据)
cancel 转发给上游停止整条链cancel 不传播(上游继续发射)
onNext 转换后发射异常时取消上游 + onError直接透传(不做转换)
onError/onComplete 透传或根据逻辑修改(如 take)吃掉终止信号

与 Reactor 操作符的对比

手写实现Reactor 等价差异
MapOperatorFlux.mapReactor 额外提供上下文传播、算子融合与错误模式
FilterOperatorFlux.filter同样在丢弃元素时补 request(1)
TakeOperatorFlux.take(n)Reactor 还支持 take(Duration)、takeUntil 等变体

手写操作符的价值不在「性能更好」,而在「理解框架在做什么」。当你遇到 Reactor 的 bug 或需要自定义操作符时,这些知识会直接派上用场。

小结

操作符链的本质是装饰器模式的层层包装:

  1. 每个操作符创建新的 Publisher,包装上游。
  2. 订阅时,下游的 Subscriber 被包装成操作符内部的 Subscriber。
  3. request 和 cancel 大多数情况透明转发。
  4. onNext 被拦截、转换、过滤或计数后,再发射给下游。

理解这个模式后,你可以:

  • 自定义任何操作符(如 distinct、debounce)。
  • 调试 Reactor/RxJava 操作符链时知道「信号走到哪一层了」。
  • 理解为什么某些操作符组合会导致意外的背压行为(如 filter 后 onComplete 提前到达)。

下一节,我们讲一个最关键的概念——subscribeOn 和 publishOn,它们决定了操作符链在哪个线程上执行。

阅读导航

上一篇:3.1 Processor:Subscriber 与 Publisher 的桥接

下一篇:3.3 调度切换:subscribeOn 与 publishOn

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「java」更多文章

  1. 15.3 全书总结与学习路径回顾
  2. 15.2 响应式流处理的演进趋势
  3. 15.1 引擎选型指南