如何将Lambda执行异常重新抛出到函数/对象的构造调用位置
你遇到的核心问题本质是异步执行逻辑的异常与构造调用点不同步:switchMap函数是在后续accept触发时才执行,异常自然只能抛到执行时的调用栈,无法直接回传到更早的构造位置。针对你的需求,有三类可行实现思路:
方案1:构造阶段预校验,直接在调用点提前抛出异常
这是最贴合你需求的方案,在mergeSwitch/forkSwitch执行(也就是你构造对象的阶段),就对传入的switchMap函数做预执行测试,只要发现返回null直接抛出异常,把错误堵在构造阶段。
改造示例:
public<S> Forkable<S> forkSwitch(Function<T, Observable<S>> switchMap) { // 构造阶段预校验:用边界测试用例执行一次switchMap Observable<S> testRes = null; try { // 优先用null入参测试,也可替换为业务场景常用的边界入参 testRes = switchMap.apply(null); } catch (Exception ignore) { // 若函数对null入参有合法性校验,替换为T类型的默认测试实例 testRes = switchMap.apply(buildDefaultTInstance()); } if (testRes == null) { throw new IllegalArgumentException("传入的switchMap函数不允许返回null"); } Forkable<S> res = new Forkable<>(); res.mergeSwitch(this, switchMap); return res; }
- 优点:完全符合你“构造调用点直接抛异常”的需求,第一时间发现非法入参
- 缺点:如果switchMap的返回值和入参强绑定,预测试可能覆盖不到所有返回null的场景,建议结合方案2使用
方案2:构造点记录调用栈,异步异常附带构造上下文
如果预校验无法覆盖所有场景,可在构造StateAwareObserver时保存当前调用栈,后续accept执行时如果遇到switchMap返回null的情况,抛出的异常附带构造阶段的栈信息,就算异常是异步触发,也能直接定位到错误的构造调用位置。
改造示例:
public<S> void mergeSwitch(Observable<S> source, Function<S, Observable<T>> switchMap) { final Mergeable<T> res = new Mergeable<>(); final Consumer<T> observer = creator(res, this); // 构造阶段记录当前调用栈,用于异常时定位构造位置 final Exception constructTrace = new Exception("switchMap构造调用栈"); setSource( source, new StateAwareObserver<S>() { Observable<T> switchRes = new Observable<>(); @Override protected void onAdded() { res.add(observer); } @Override protected void onRemoved() { res.remove(observer); } @Override public void accept(S s) { Observable<T> switchS = switchMap.apply(s); // 提前判空,抛出带构造栈的异常 if (switchS == null) { NullPointerException ex = new NullPointerException("switchMap返回了null"); ex.addSuppressed(constructTrace); throw ex; } if (switchRes != switchS) { switchRes = switchS; res.merge(switchS); } } } ); }
- 优点:无需预执行,100%覆盖所有返回null的场景,异常信息直接携带构造点调用栈,调试时可直接定位到传入非法lambda的代码行,实际使用效果和构造点抛异常几乎一致
- 缺点:异常还是在异步执行阶段抛出,不会阻塞构造流程
方案3:编译期静态校验,从根源杜绝null返回
不需要运行时逻辑,通过静态检查工具在编码阶段直接拦截返回null的情况:
- 给Function的返回值加上
@NonNull注解:public<S> Forkable<S> forkSwitch(Function<T, @NonNull Observable<S>> switchMap) - 开启IDE的非空检查,或集成Checker Framework、SpotBugs这类静态检查工具,只要写了返回null的lambda,编译阶段直接报错,无需运行就能发现问题。
内容的提问来源于stack exchange,提问作者Delark
相关产品推荐
相关产品推荐

