【问题标题】:Rethrowing exceptions at place of function/object construction在函数/对象构造的地方重新抛出异常
【发布时间】:2021-10-31 07:47:18
【问题描述】:

在函数的情况下,当异常可能只在它们的执行位置抛出(基于我缺乏知识,这对我来说目前还不确定),有时罪魁祸首可能取决于创建 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&lt;S&gt; source, Function&lt;S, Observable&lt;T&gt;&gt; switchMap) 它创建了消费者,可能遗漏了一些重要的东西。

或者就我而言,执行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
        )
);

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

【问题讨论】:

    标签: java function exception rethrow


    【解决方案1】:

    编辑:

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

    最佳答案

        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: 切换映射函数不能返回 空值!!在(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;
        }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-07-02
      • 2019-07-11
      • 1970-01-01
      • 1970-01-01
      • 2011-11-04
      • 2011-08-04
      • 1970-01-01
      • 2012-01-21
      相关资源
      最近更新 更多