0

在函数的情况下,当异常可能只在它们的执行位置抛出时(基于我缺乏知识,这对我来说目前还不确定),有时罪魁祸首可能依赖于 lambda 的创建方式,一个例子可能是:

    // merge() will throw an exception if Function<S, Observable<T>> switchMap returns 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); // Secondary subscription

        setSource(
                source, // Main subscription
                new StateAwareObserver<S>() { // >>> implements Consumer<S>
                    Observable<T> switchRes = new Observable<>(); // Mediator

                    @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 (switchRes != switchS) {
                             switchRes = switchS;
                             res.merge(switchS); //Where merge() throws if switchMap returns null
                         }
                    }
                }
        );
    }
}

我尝试从 accept() 方法中重新抛出异常,但它永远不会被推(拉?)超出其消费点(消费者接受“s”的地方):

        for (int i = observers.size() - 1; i >=0 ; i--) {
            observers.get(i).accept(processed); // << -- StateAwareObserver<S> is stored in the *list*
        }

- 在这种情况下,我使用“超越”这个词,但我需要的是在其构造点抛出异常。-

实际上,真正的罪魁祸首可能是方法的执行:mergeSwitch(Observable<S> source, Function<S, Observable<T>> switchMap) 它创建了 Consumer<>,可能遗漏了一些重要的东西。

或者就我而言,执行mergeSwitch()的方法:

    public<S> Forkable<S> forkSwitch(Function<T, Observable<S>> switchMap) {
        Forkable<S> res = new Forkable<>();
        res.mergeSwitch(this, switchMap);
        return res;
    }

在这种情况下,问题是该函数留下了一个空值。

public Holders.Forkable<List<Preset>> ledgerPresets = presetDao.forkSwitch(
        presetDao -> ledgerId.forkSwitch(
                aLong -> null
        )
);

因此,如果我们看一下消费者级别,真正的罪魁祸首是:

                    @Override
                    public void accept(S s) {
                        int sVersion = source.getVersion();
                        if (sVersion != oVersion) {
                            Observable3<T> switchS = switchMap.apply(s); //< -- HERE
                            if (switchRes != switchS) {
                                switchRes = switchS;
                                res.merge(switchS);
                            }
                            oVersion = sVersion;
                        }
                    }

在这种特殊情况下,事情很容易失控,因为日志可能不会在最近的消费点停止,但错误可能会一直回到根节点。

事情可能会变得更加复杂,因为作为异常起源的 merge() 方法可能已从 3 种不同的方法中使用。

理想的情况是在此处抛出异常:

public Holders.Forkable<List<Preset>> ledgerPresets = presetDao.forkSwitch(
        presetDao -> ledgerId.forkSwitch(   // <--- HERE
                aLong -> null
        )
);

但是我还没有找到一种方法来管理这个特殊的异常重新抛出案例。

4

1 回答 1

0

编辑:

在我之前的回答中,我花了很多时间尝试将异常推送到堆栈跟踪级别,以至于我只是停止尝试以简单的方式进行操作,您可以忽略新答案下面的疯狂答案:

最佳答案

    public<S> Forkable<S> forkSwitch(Function<T, Observable<S>> switchMap) {
        final IllegalStateException e = new IllegalStateException("switchMap must not be null");

        final Forkable<S> res = new Forkable<>();
        res.mergeSwitch(this,
                t -> {
                    Observable<S> prev = switchMap.apply(t);
                    if (prev == null) {
                        throw e;
                    }
                    return prev;
                }
        );
        return res;
    }

旧答案

我试图寻找答案,但没有找到理想的答案。

我的解决方案是捕获一个存储顶部堆栈跟踪的 int,然后通过 lambda 推断类名。

    public<S> Forkable2<S> forkSwitch(Function<T, Observable3<S>> switchMap) {
        int lineNumber = Thread.currentThread().getStackTrace()[3].getLineNumber();
        Forkable2<S> res = new Forkable2<>();
        res.mergeSwitch(this,
                t -> {
                    Observable3<S> prev = switchMap.apply(t);
                    if (prev == null) {
                        String lambdaS = switchMap.toString();
                        String prevLambdaS = lambdaS.substring(0, lambdaS.indexOf("$"));
                        throw new IllegalStateException("Switch map function must not return null!! at(" + prevLambdaS.substring(prevLambdaS.lastIndexOf(".") + 1) + ".java:"+ lineNumber + "), \n" +
                                "from: " + this);
                    }
                    return prev;
                }
        );
        return res;
    }

我觉得这个解决方案乏善可陈的原因是异常的起源实际上没有被使用,我们真正在做的是在事件实际发生之前拦截事件,但是 imo 这带来了冗余。

幸运的是,这个例外现在比以前更清楚了:

java.lang.IllegalStateException:切换映射函数不能返回null!!在(PresetViewModel.java:66),

我相信可以将整个过程封装在一个简洁的小对象中,该对象在输入 lambda 之前创建堆栈跟踪,然后在 lambda 本身内部需要时创建命令异常。

public<T, S> void toThrow(Function<T, S> throwableLambda) {
    InferableThrow iT = new InferableThrow();
    subscribe(
        t -> {
            S returned = throwableLambda.apply(t);
            if (returned == null) {
                throw iT.createExc(Class<? extends Exception>, Function<>, String message);
            }
            return returned;
        }
    );
}

这是一个辅助对象:

public final class InferableException<E extends RuntimeException> implements Supplier<E> {
    private final Supplier<E> type;

    public static<E extends RuntimeException> InferableException<E> create(
            Function<String, E> type, Object lambdaObject, String message
    ) {
        return new InferableException<>(type, lambdaObject, message);
    }

    private InferableException(Function<String, E> type, Object lambdaObject, String message) {
        int lineNumber = Thread.currentThread().getStackTrace()[5].getLineNumber();
        String lambdaS = lambdaObject.toString();
        String prevLambdaS = lambdaS.substring(0, lambdaS.indexOf("$"));
        String className = prevLambdaS.substring(prevLambdaS.lastIndexOf(".") + 1);
        this.type = () -> type.apply(message +" at("+ className + ".java:" + lineNumber + ")");
    }

    @Override
    public E get() {
        return type.get();
    }
}

用法:

    public<S> Forkable<S> forkSwitch(Function<T, Observable<S>> switchMap) {
        final Supplier<IllegalStateException> e = InferableException.create( //<<-- construction
                IllegalStateException::new,
                switchMap,
                "Switch map function must not return null!!"
        );
        Forkable<S> res = new Forkable<>();
        res.mergeSwitch(this,
                t -> {
                    Observable<S> prev = switchMap.apply(t);
                    if (prev == null) {
                        throw e.get();   //<<-- Throwing!!
                    }
                    return prev;
                }
        );
        return res;
    }
于 2021-09-01T21:55:03.140 回答