Lambda从入门到精通之三十四 CompletableFuture异步编程 whenComplete方法使用详解

CompletableFuture的whenComplete方法可以对结果再次进行处理,如果正常返回结果则返回,如果异常则可以定义异常处理逻辑进行处理。
CompletableFuture whenComplete(BiConsumer action)方法的参数是一个二元的消费函数,处理函数有两个参数void accept(T t, U u),第一个是业务逻辑处理结果,第二个是异常结果,二者其中一个总是为null。

我们先来看一个demo,对比一下任务的输出结果:

import java.util.concurrent.CompletableFuture;

/**
 * 使用CompletableFuture异步编程 whenComplete
 * @author www.itzhimei.com
 */
public class FutureTest_9 {

    public static void main(String[] args) throws Exception {

        //正常执行
        String join = CompletableFuture.supplyAsync(() -> FutureTest_9.getTaskResult(500, "Test Task 2"))
                .whenComplete((x, e) -> {
                    System.out.println("计算结果:" + x);
                    System.out.println("异常信息:" + e);
                }).join();
        System.out.println(join);
        /* 输出
        计算结果:Test Task 2
        异常信息:null
        Test Task 2
         */

        //执行异常
        String join2 = CompletableFuture.supplyAsync(() -> FutureTest_9.getTaskResultWithException(500, "Test Task 2"))
                .whenComplete((x, e) -> {
                    System.out.println("计算结果:" + x);
                    System.out.println("异常信息:" + e);
                }).join();
        System.out.println(join2);

        /* 输出
        Exception in thread "main" java.util.concurrent.CompletionException: java.lang.RuntimeException: 执行异常了
            at java.util.concurrent.CompletableFuture.internalComplete(CompletableFuture.java:205)
            at java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:482)
            at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
            at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:902)
            at java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1689)
            at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1644)
            at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
        Caused by: java.lang.RuntimeException: 执行异常了
            at com.itzhimei.FutureTest_9.getTaskResultWithException(FutureTest_9.java:37)
            at com.itzhimei.FutureTest_9.lambda$main$0(FutureTest_9.java:13)
            at com.itzhimei.FutureTest_9$$Lambda$1/455896770.get(Unknown Source)
            at java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:476)
            ... 5 more
        计算结果:null
        异常信息:java.util.concurrent.CompletionException: java.lang.RuntimeException: 执行异常了
         */

    }

    public static String getTaskResult(long sleepTime, String task) {
        try {
            Thread.sleep(sleepTime);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return task;
    }

    public static String getTaskResultWithException(long sleepTime, String task) {
        try {
            Thread.sleep(sleepTime);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        throw new RuntimeException("执行异常了");
        //return task;
    }

}
/* 输出
计算结果:Test Task 2
异常信息:null
Test Task 2

Exception in thread "main" java.util.concurrent.CompletionException: java.lang.RuntimeException: 执行异常了
	at java.util.concurrent.CompletableFuture.internalComplete(CompletableFuture.java:205)
	at java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:482)
	at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
	at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:902)
	at java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1689)
	at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1644)
	at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
Caused by: java.lang.RuntimeException: 执行异常了
	at com.itzhimei.base.lambda.future.FutureTest_9.getTaskResultWithException(FutureTest_9.java:65)
	at com.itzhimei.base.lambda.future.FutureTest_9.lambda$main$2(FutureTest_9.java:22)
	at com.itzhimei.base.lambda.future.FutureTest_9$$Lambda$3/932172204.get(Unknown Source)
	at java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:476)
	... 5 more
计算结果:null
异常信息:java.util.concurrent.CompletionException: java.lang.RuntimeException: 执行异常了
 */

如果我们使用whenComplete方法,处理异常时将异常抛出,则后续代码是无法执行的,因为发生了异常,如果想后续代码继续执行,有两种方法:
1、在whenComplete方法中忽略异常,即没有代码去处理异常,代码会继续后续执行。
2、在whenComplete方法后面继续添加一个异常处理方法,这也是CompletableFuture的大多数方法都可行的异常处理方案。
我们来看方案二的代码:

/**
 * 使用CompletableFuture异步编程 exceptionally
 * @author www.itzhimei.com
 */
public class FutureTest_10 {

    public static void main(String[] args) throws Exception {

        //正常执行
        String join = CompletableFuture.supplyAsync(() -> FutureTest_10.getTaskResult(500, "Test Task 2"))
                .whenComplete((x, e) -> {
                    System.out.println("计算结果:" + x);
                    System.out.println("异常信息:" + e);
                }).join();
        System.out.println(join);
        /* 输出
        计算结果:Test Task 2
        异常信息:null
        Test Task 2
         */

        //执行异常,捕获异常并处理
        String join3 = CompletableFuture.supplyAsync(() -> FutureTest_10.getTaskResultWithException(500, "Test Task 2"))
                .whenComplete((x, e) -> {
                    System.out.println("计算结果:" + x);
                    System.out.println("异常信息:" + e);
                }).exceptionally(e-> "捕获异常,返回为空").join();
        System.out.println(join3);

    }

    public static String getTaskResult(long sleepTime, String task) {
        try {
            Thread.sleep(sleepTime);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return task;
    }

    public static String getTaskResultWithException(long sleepTime, String task) {
        try {
            Thread.sleep(sleepTime);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        throw new RuntimeException("执行异常了");
        //return task;
    }

}

/* 输出
计算结果:Test Task 2
异常信息:null
Test Task 2

计算结果:null
异常信息:java.util.concurrent.CompletionException: java.lang.RuntimeException: 执行异常了
捕获异常,返回为空
 */
whenComplete有3个版本的方法,区别就在于是在当前线程执行还是使用线程池中的新线程执行
CompletableFuture<T> whenComplete(BiConsumer<? super T,? super Throwable> action) 
		Returns a new CompletionStage with the same result or exception as this stage, and when this stage completes, executes the given action with the result (or null if none) and the exception (or null if none) of this stage. 
CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action) 
		Returns a new CompletionStage with the same result or exception as this stage, and when this stage completes, executes the given action executes the given action using this stage's default asynchronous execution facility, with the result (or null if none) and the exception (or null if none) of this stage as arguments. 
CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action, Executor executor) 
		Returns a new CompletionStage with the same result or exception as this stage, and when this stage completes, executes using the supplied Executor, the given action with the result (or null if none) and the exception (or null if none) of this stage as arguments.