【问题标题】:Throwing checked exceptions with CompletableFuture使用 CompletableFuture 抛出检查异常
【发布时间】:2018-04-07 08:13:39
【问题描述】:

Stackoverflow 包含多个关于将检查的异常与 CompletableFuture 混合的问题。

这里有几个例子:

虽然一些答案暗示使用CompletableFuture.completeExceptionally(),但他们的方法导致用户代码难以阅读。

我将利用这个空间来提供一个替代解决方案,以提高可读性。

请注意,这个问题是 CompletableFuture 特有的。这使我们能够提供不更普遍地扩展到 lambda 表达式的解决方案。

【问题讨论】:

    标签: java completable-future


    【解决方案1】:

    鉴于Completions 实用程序类(如下提供),用户可以无缝抛出已检查异常:

    public CompletionStage<String> readLine()
    {
      return Completions.supplyAsync(() ->
      {
        try (BufferedReader br = new BufferedReader(new FileReader("test.txt")))
        {
          return br.readLine();
        }
      });
    }
    

    任何由 lambda 抛出的异常(检查与否)都将包装在 CompletionException 中,这与 CompletableFuture 对未检查异常的行为一致。

    对于像thenApply() 这样的中间步骤,事情会变得有点丑陋,但这并不是世界末日:

    public CompletionStage<String> transformLine()
    {
      return readLine().thenApply(line ->
        Completions.wrapExceptions(() ->
        {
          if (line.contains("%"))
            throw new IOException("Lines may not contain '%': " + line);
          return "transformed: " + line;
        }));
    }
    

    这里有一些来自Completions 实用程序类的方法。您可以通过这种方式包装其他 CompletableFuture 方法。

    /**
     * Helper functions for {@code CompletionStage}.
     *
     * @author Gili Tzabari
     */
    public final class Completions
    {
        /**
         * Returns a {@code CompletionStage} that is completed with the value or exception of the {@code CompletionStage}
         * returned by {@code callable} using the supplied {@code executor}. If {@code callable} throws an exception the
         * returned {@code CompletionStage} is completed with it.
         *
         * @param <T>      the type of value returned by {@code callable}
         * @param callable returns a value
         * @param executor the executor that will run {@code callable}
         * @return the value returned by {@code callable}
         */
        public static <T> CompletionStage<T> supplyAsync(Callable<T> callable, Executor executor)
        {
            return CompletableFuture.supplyAsync(() -> wrapExceptions(callable), executor);
        }
    
        /**
         * Wraps or replaces exceptions thrown by an operation with {@code CompletionException}.
         * <p>
         * If the exception is designed to wrap other exceptions, such as {@code ExecutionException}, its underlying cause is wrapped; otherwise the
         * top-level exception is wrapped.
         *
         * @param <T>      the type of value returned by the callable
         * @param callable an operation that returns a value
         * @return the value returned by the callable
         * @throws CompletionException if the callable throws any exceptions
         */
        public static <T> T wrapExceptions(Callable<T> callable)
        {
            try
            {
                return callable.call();
            }
            catch (CompletionException e)
            {
                // Avoid wrapping
                throw e;
            }
            catch (ExecutionException e)
            {
                throw new CompletionException(e.getCause());
            }
            catch (Throwable e)
            {
                throw new CompletionException(e);
            }
        }
    
        /**
         * Returns a {@code CompletionStage} that is completed with the value or exception of the {@code CompletionStage}
         * returned by {@code callable} using the default executor. If {@code callable} throws an exception the returned
         * {@code CompletionStage} is completed with it.
         *
         * @param <T>      the type of value returned by the {@code callable}
         * @param callable returns a value
         * @return the value returned by {@code callable}
         */
        public static <T> CompletionStage<T> supplyAsync(Callable<T> callable)
        {
            return CompletableFuture.supplyAsync(() -> wrapExceptions(callable));
        }
    
        /**
         * Prevent construction.
         */
        private Completions()
        {}
    }
    

    【讨论】:

    • 我不确定捕获所有Throwable 类型并包装在CompletionException 中是否是个好主意。特别是,我会认为这对java.lang.Error 尤其不利。
    • @msandiford 如果您深入研究CompletableFuture 的实现,您会发现它们的作用相同(例如,在uniApply() 方法中)。用户希望能够记录AssertionError 之类的错误,否则他们的代码只会默默地失败,而且他们不知道出了什么问题。
    • 也许我错过了您要提出的观点,但CompletableFuture 机器不处理这种情况吗?在什么情况下AssertionError(例如)会被吞下,而不会在管道中的某处导致用户可见的ExecutionException
    • @msandiford CompletableFuture 机器处理这种情况的原因是因为它捕获了Throwable。这个包装器必须提供与底层实现相同的功能plus 支持检查异常。因此,如果底层实现捕获Throwable,那么包装器也必须捕获。
    猜你喜欢
    • 2017-11-08
    • 2017-01-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-08
    • 2016-07-21
    • 1970-01-01
    相关资源
    最近更新 更多