天天看點

【代碼品質】-借助JDK8提供的CompletableFuture寫出優雅的代碼1.建立CompletableFuture對象2.同步擷取計算結果3.計算完成後的處理4.處理多階段任務5.組合任務6.Tips7.代碼示範8.總結

前言:要寫一手優雅的代碼,背後要經曆很多的積累和沉澱,有點類似台上一分鐘,台下十年功.要讓代碼變得優雅有很多種方式,使用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);
    }
}
           

運作結果:

【代碼品質】-借助JDK8提供的CompletableFuture寫出優雅的代碼1.建立CompletableFuture對象2.同步擷取計算結果3.計算完成後的處理4.處理多階段任務5.組合任務6.Tips7.代碼示範8.總結

8.總結

CompletableFuture封裝的真的好,以至于我在寫上面這段代碼時都不想寫下去了,真的太簡單了,沒啥好示範的,隻要有jdk8的lambda基礎和functional接口基礎,就可以輕松上手,之是以寫這篇也是為了做個筆記,總結下然後加深印象,感謝你的閱讀,文中若有不正之處,歡迎留言斧正。

繼續閱讀