【代码质量】-借助JDK8提供的CompletableFuture写出优雅的代码

前言:要写一手优雅的代码,背后要经历很多的积累和沉淀,有点类似台上一分钟,台下十年功.要让代码变得优雅有很多种方式,使用JDK8提供的新特性便是其中一种,虽然相较于设计模式对代码质量提升有限,但仍值得去学习和使用.本篇仅介CompletableFuture.


 

早在JDK1.5中就提供了Future和Callable来获取异步任务的结果,但因为获取结果阻塞的原因,并没有真正实现异步带来的价值.体验非常不好,于是JDK在1.8中终于出了Future的增强版CompletableFuture,借助CompletableFuture提供的异步能力和lambda表达式风格,可以写出优雅的异步编程代码. 

 


目录

 

1.创建CompletableFuture对象

2.同步获取计算结果

3.计算完成后的处理

4.处理多阶段任务

5.组合任务

6.Tips

7.代码演示

8.总结


1.创建CompletableFuture对象

CompletableFuture提供了4个静态方法用于创建CompletableFuture实例:

CompletableFuture<Void> runAsync(Runnable runnable)
CompletableFuture<Void> runAsync(Runnable runnable,Executor executor)

CompletableFuture<U> supplyAsync(Supplier<U> supplier)
CompletableFuture<U> supplyAsync(Supplier<U> supplier,Executor executor)

其中带Executor参数的用于指定自定义异步执行线程池,如果不指定则默认使用JDK提供的FrokJoinPool.commonPool().

如果无需关心执行后的结果,使用runAsync创建即可,否则使用supplyAsync创建CompletableFuture.

2.同步获取计算结果

CompletableFuture提供4种用于同步等待获取计算结果的方法,这点跟老的Future没啥两样,只不过额外提供了getNow和join方法.

public T    get()
public T    get(long timeout, TimeUnit unit)
public T    getNow(T valueIfAbsent)
public T    join()

getNow用于立即获取返回结果,如果此时被调用方法还没返回值,则返回指定的valueIfAbsent.

join方法和get作用一样,用于阻塞获取执行结果,不同的是join在调用方法出现异常时仅会返回uncheckedException,而get则返回具体异常.

3.计算完成后的处理

CompletableFuture可以通过whenComplete/whenCompleteAsync来获取计算结果并处理,不再需要阻塞获取,而是以异步通知回调的方式.

public CompletableFuture<T>     whenComplete(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T>     whenCompleteAsync(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T>     whenCompleteAsync(BiConsumer<? super T,? super Throwable> action, Executor executor)
public CompletableFuture<T>     exceptionally(Function<Throwable,? extends T> fn)

 

4.处理多阶段任务

如果一个任务需要多个阶段才能最终完成,那么可以采用thenApply这种方式链式调用完成整个任务.

public <U> CompletableFuture<U>     thenApply(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U>     thenApplyAsync(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U>     thenApplyAsync(Function<? super T,? extends U> fn, Executor executor)

也可以采用handle方式:

public <U> CompletableFuture<U>     handle(BiFunction<? super T,Throwable,? extends U> fn)
public <U> CompletableFuture<U>     handleAsync(BiFunction<? super T,Throwable,? extends U> fn)
public <U> CompletableFuture<U>     handleAsync(BiFunction<? super T,Throwable,? extends U> fn, Executor executor)

与thenApply不同的是,handle是在任务完成后再执行,且可以处理异常,thenApply遇到异常则会抛出.

如果任务无需返回,只需要处理结果,那么可以使用消费者

public CompletableFuture<Void>  thenAccept(Consumer<? super T> action)
public CompletableFuture<Void>  thenAcceptAsync(Consumer<? super T> action)
public CompletableFuture<Void>  thenAcceptAsync(Consumer<? super T> action, Executor executor)

5.组合任务

thenAcceptBoth用于组合两个任务,当两个任务都计算完成时执行:

public <U> CompletableFuture<Void> thenAcceptBoth(CompletionStage<? extends U> other,
        BiConsumer<? super T, ? super U> action)
public <U> CompletableFuture<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,
        BiConsumer<? super T, ? super U> action)

thenAcceptEither只要两个任务中有一个先完成了即执行:

    public <U> CompletableFuture<U> applyToEither(
        CompletionStage<? extends T> other, Function<? super T, U> fn)

如果要组合的任务超过2个,可以用:

任意一个完成:

public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs)

全部完成:

public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs)

6.Tips

JDK8CompletableFuture API中提供了五十多个静态方法,乍看上去很多,其实实际开发中经常被用到的连一半都不到,再加上大部分其实是方法重载,以及功能类似方法,抽象一下最后只需要掌握十个以内的方法,便可以驾驭CompletableFuture写出优雅代码.

创建CompletableFuture对象时,以run打头的方法无返回值,以supply打头的方法有返回,可以对返回结果进一步处理.

处理任务时,方法名中包含Apply的方法有返回值,是生产者,方法名中包含Accept的方法无返回值,是消费者.

以Async结尾的方法表明该方法异步执行.


7.代码演示

上面都是纯纯的理论部分,并没有结合实际代码演示,所以下面正式结合实际代码演示之:

几种经常会用到场景如下所示,如果不需要处理返回结果,可以将Apply替换为Accept作为纯消费者即可

public class Client {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        //case1:任务需要多个阶段才能完成,完成执行后续动作...
        CompletableFuture.supplyAsync(() -> "hello")
                .thenApplyAsync(s -> s + " ")
                .thenApplyAsync(s -> s + "world")
                .thenApplyAsync(String::toUpperCase)
                .whenCompleteAsync((result, throwable) -> System.out.println(result));

        //case2:完成任务需要调用rpc接口,有很多个rpc接口,只要其中一个调用成功即算完成任务
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "hello");
        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
            try {
                //模拟调用延迟
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "world";
        });
        //省略其它rpc接口...
        CompletableFuture<Object> res = CompletableFuture.anyOf(future1, future2);
        System.out.println(res.get());

        //case3:完成任务需要所有的rpc接口都完成才算完成
        //如果使用allOf是没有返回结果的
        CompletableFuture<Void> all = CompletableFuture.allOf(future1, future2);
        System.out.println(all.get());
        //所以如果需要获取最终返回的结果,需要手动处理异步返回的结果
        String combine = Stream.of(future1, future2)
                .map(CompletableFuture::join)
                .collect(Collectors.joining());
        System.out.println(combine);
    }
}

运行结果:

 

8.总结

CompletableFuture封装的真的好,以至于我在写上面这段代码时都不想写下去了,真的太简单了,没啥好演示的,只要有jdk8的lambda基础和functional接口基础,就可以轻松上手,之所以写这篇也是为了做个笔记,总结下然后加深印象,感谢你的阅读,文中若有不正之处,欢迎留言斧正。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章