使用CompletableFuture处理Java 8供应商异常

时间:2015-03-10 09:16:16

标签: java exception java-8 completable-future

考虑以下代码

public class TestCompletableFuture {

    BiConsumer<Integer, Throwable> biConsumer = (x,y) -> {
        System.out.println(x);
        System.out.println(y);
    };

    public static void main(String args[]) {
        TestCompletableFuture testF = new TestCompletableFuture();
        testF.start();      
    }

    public void start() {
        Supplier<Integer> numberSupplier = new Supplier<Integer>() {
            @Override
            public Integer get() {
                return SupplyNumbers.sendNumbers();                     
            }
        };
        CompletableFuture<Integer> testFuture = CompletableFuture.supplyAsync(numberSupplier).whenComplete(biConsumer);         
    }       
}

class SupplyNumbers {
    public static Integer sendNumbers(){
        return 25; // just for working sake its not  correct.
    }
}

以上的事情很好。但是sendNumbers也可以在我的情况下抛出一个检查过的异常,例如:

class SupplyNumbers {
    public static Integer sendNumbers() throws Exception {
        return 25; // just for working sake its not  correct.
    }
}

现在我想在我的y中以biConsumer处理此异常。这将帮助我在单个函数(biConsumer)中处理结果以及异常(如果有的话)。

有什么想法吗?我可以在这里或其他任何地方使用CompletableFuture.exceptionally(fn)吗?

5 个答案:

答案 0 :(得分:14)

当您想要处理已检查的异常时,使用标准功能接口的工厂方法没有用。当您将捕获异常的代码插入lambda表达式时,您遇到的问题是catch子句需要CompletableFuture实例来设置异常,而工厂方法需要Supplier,鸡蛋和鸡蛋。

您可以使用类的实例字段在创建后允许变异,但最终,生成的代码不是干净的,而且比基于Executor的直接解决方案更复杂。 documentation of CompletableFuture说:

  

因此,您知道以下代码将直接处理已检查的异常时显示CompletableFuture.supplyAsync(Supplier)的标准行为:

CompletableFuture<Integer> f=new CompletableFuture<>();
ForkJoinPool.commonPool().submit(()-> {
  try { f.complete(SupplyNumbers.sendNumbers()); }
  catch(Exception ex) { f.completeExceptionally(ex); }
});

文档还说:

  

...为了简化监控,调试和跟踪,所有生成的异步任务都是标记接口CompletableFuture.AsynchronousCompletionTask的实例。

如果您希望遵守此约定以使解决方案更像原始supplyAsync方法,请将代码更改为:

CompletableFuture<Integer> f=new CompletableFuture<>();
ForkJoinPool.commonPool().submit(
  (Runnable&CompletableFuture.AsynchronousCompletionTask)()-> {
    try { f.complete(SupplyNumbers.sendNumbers()); }
    catch(Exception ex) { f.completeExceptionally(ex); }
});

答案 1 :(得分:7)

您已在y中捕获该异常。也许你没有看到它,因为main在CompletableFuture有机会完成之前退出了?

下面的代码打印&#34; null&#34;和#34;您好&#34;正如所料:

public static void main(String args[]) throws InterruptedException {
  TestCompletableFuture testF = new TestCompletableFuture();
  testF.start();
  Thread.sleep(1000); //wait for the CompletableFuture to complete
}

public static class TestCompletableFuture {
  BiConsumer<Integer, Throwable> biConsumer = (x, y) -> {
    System.out.println(x);
    System.out.println(y);
  };
  public void start() {
    CompletableFuture.supplyAsync(SupplyNumbers::sendNumbers)
            .whenComplete(biConsumer);
  }
}

static class SupplyNumbers {
  public static Integer sendNumbers() {
    throw new RuntimeException("Hello");
  }
}

答案 2 :(得分:3)

我不太确定你想要实现的目标。如果您的供应商抛出异常,当您致电testFuture .get()时,您会因供应商抛出的任何异常而导致java.util.concurrent.ExecutionException,您可以通过getCause()上的ExecutionException进行检索}。

或者,正如您所提到的,您可以在exceptionally中使用CompletableFuture。这段代码:

public class TestCompletableFuture {

    private static BiConsumer<Integer, Throwable> biConsumer = (x,y) -> {
        System.out.println(x);
        System.out.println(y);
    };

    public static void main(String args[]) throws Exception {
        Supplier<Integer> numberSupplier = () -> {
            throw new RuntimeException(); // or return integer
        };

        CompletableFuture<Integer> testFuture = CompletableFuture.supplyAsync(numberSupplier)
                .whenComplete(biConsumer)
                .exceptionally(exception -> 7);

        System.out.println("result = " + testFuture.get());
    }

}

打印此结果:

null
java.util.concurrent.CompletionException: java.lang.RuntimeException
result = 7

修改

如果您检查过例外,您只需添加一个try-catch。

原始代码:

Supplier<Integer> numberSupplier = new Supplier<Integer>() {
    @Override
    public Integer get() {
        return SupplyNumbers.sendNumbers();                     
    }
};

修改后的代码:

Supplier<Integer> numberSupplier = new Supplier<Integer>() {
    @Override
    public Integer get() {
        try {
            return SupplyNumbers.sendNumbers();                     
        } catch (Excetpion e) {
            throw new RuntimeExcetpion(e);
        }
    }
};

答案 3 :(得分:1)

也许您可以使用新的Object来包装整数和错误,如下所示:

public class Result {

    private Integer   integer;
    private Exception exception;

    // getter setter

}

然后:

public void start(){
    Supplier<Result> numberSupplier = new Supplier<Result>() {
        @Override
        public Result get() {
            Result r = new Result();
            try {
                r.setInteger(SupplyNumbers.sendNumbers());
            } catch (Exception e){
                r.setException(e);
            }
            return r;

        }
    };
    CompletableFuture<Result> testFuture = CompletableFuture.supplyAsync(numberSupplier).whenComplete(biConsumer);
}

答案 4 :(得分:1)

使用CompletableFuture时要考虑到completeExceptionally()中的异常处理的另一点是,确切的异常将在handle()whenComplete()中可用,但将其包装在CompletionException中调用join()或将其转发到任何下游阶段时。

应用于下游阶段的handle()exceptionally()将因此看到CompletionException而不是原始的RuntimeException,并且必须查看其原因以找到原始的异常。 / p>

此外,由任何操作(包括supplyAsync())引发的任何CompletionException都被包装在CompletionException中,除非它已经是CompletionException

考虑到这一点,最好在安全方面发挥作用,并让异常处理程序解开CompletableFuture

如果您这样做,就再也没有必要在CompletionException上设置确切的(检查的)异常了,将检查的异常直接包装在Supplier<Integer> numberSupplier = () -> { try { return SupplyNumbers.sendNumbers(); } catch (Exception e) { throw new CompletionException(e); } }; 中要简单得多:

simpleWrap()

为了将此方法与Holger's approach进行比较,我使用两种解决方案修改了您的代码(customWrap()以上,public class TestCompletableFuture { public static void main(String args[]) { TestCompletableFuture testF = new TestCompletableFuture(); System.out.println("Simple wrap"); testF.handle(testF.simpleWrap()); System.out.println("Custom wrap"); testF.handle(testF.customWrap()); } private void handle(CompletableFuture<Integer> future) { future.whenComplete((x1, y) -> { System.out.println("Before thenApply(): " + y); }); future.thenApply(x -> x).whenComplete((x1, y) -> { System.out.println("After thenApply(): " + y); }); try { future.join(); } catch (Exception e) { System.out.println("Join threw " + e); } try { future.get(); } catch (Exception e) { System.out.println("Get threw " + e); } } public CompletableFuture<Integer> simpleWrap() { Supplier<Integer> numberSupplier = () -> { try { return SupplyNumbers.sendNumbers(); } catch (Exception e) { throw new CompletionException(e); } }; return CompletableFuture.supplyAsync(numberSupplier); } public CompletableFuture<Integer> customWrap() { CompletableFuture<Integer> f = new CompletableFuture<>(); ForkJoinPool.commonPool().submit( (Runnable & CompletableFuture.AsynchronousCompletionTask) () -> { try { f.complete(SupplyNumbers.sendNumbers()); } catch (Exception ex) { f.completeExceptionally(ex); } }); return f; } } class SupplyNumbers { public static Integer sendNumbers() throws Exception { throw new Exception("test"); // just for working sake its not correct. } } 是Holger的代码):

Simple wrap
After thenApply(): java.util.concurrent.CompletionException: java.lang.Exception: test
Before thenApply(): java.util.concurrent.CompletionException: java.lang.Exception: test
Join threw java.util.concurrent.CompletionException: java.lang.Exception: test
Get threw java.util.concurrent.ExecutionException: java.lang.Exception: test
Custom wrap
After thenApply(): java.util.concurrent.CompletionException: java.lang.Exception: test
Before thenApply(): java.lang.Exception: test
Join threw java.util.concurrent.CompletionException: java.lang.Exception: test
Get threw java.util.concurrent.ExecutionException: java.lang.Exception: test

输出:

whenComplete()

您会注意到,唯一的区别是在thenApply()情况下,customWrap()thenApply()之前看到了原始异常。在get()之后,在所有其他情况下,原始异常都被包装。

最令人惊讶的是,CompletionException将在“简单包装”的情况下解开ExecutionException,并将其替换为 <app-datepicker1 (date1)="catchDate1($event)"></app-datepicker1> <app-datepicker2 (date2)="catchDate2($event)"></app-datepicker2>