Java 函數(shù)式編程

前些年 Scala 大肆流行,打出來(lái) Java 顛覆者的旗號(hào),究其底氣來(lái)源,無(wú)非是函數(shù)式和面向?qū)ο蟮摹巴昝澜Y(jié)合”,各式各樣的“語(yǔ)法糖”,但其過(guò)高的學(xué)習(xí)門檻,又給了新來(lái)者當(dāng)頭一棒。

隨著 Java8 的發(fā)布,Lambda 特性的引入,之前的焦灼局面是否有所轉(zhuǎn)變,讓我們一起揭開 Java 函數(shù)式編程的面紗:

  1. 面向?qū)ο?VS 函數(shù)式
  2. FunctionalInterface 和 Lambda
  3. 類庫(kù)的升級(jí)改造(默認(rèn)方法、靜態(tài)方法、Stream、Optional)
  4. Lambda 下模式的進(jìn)化
  5. Lambda 下并發(fā)程序

1. 面向?qū)ο?VS 函數(shù)式編程

一句話總結(jié)兩種的關(guān)系:面向?qū)ο缶幊淌菍?duì)數(shù)據(jù)進(jìn)行抽象;而函數(shù)式編程是對(duì)行為進(jìn)行抽象。

在現(xiàn)實(shí)世界中,數(shù)據(jù)和行為并存,程序也應(yīng)如此,可喜可賀的是在 Java 世界中,兩者也開啟了融合之旅。

首先思考一個(gè)問(wèn)題, 在 Java 編程中,我們?nèi)绾芜M(jìn)行行為傳遞,例如我們需要打印線程名稱和當(dāng)前時(shí)間,并將該任務(wù)提交到線程池中運(yùn)行,會(huì)有哪些方法?

方法 1:新建 class Task 實(shí)現(xiàn) Runnable 接口

    public class Task implements Runnable{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "-->" + System.currentTimeMillis() + "ms");
        }
    }
    executorService.submit(new Task());

方法 2:匿名內(nèi)部類實(shí)現(xiàn) Runnable 接口

    executorService.submit(new Runnable() {
            @Override
            public void run() {
              System.out.println(Thread.currentThread().getName() + "-->" + System.currentTimeMillis() + "ms");
            }
        });

方法 3:使用 Lambda 表達(dá)式

    executorService.submit(()-> System.out.println(Thread.currentThread().getName() + "-->" + System.currentTimeMillis() + "ms"));

方法 4:使用方法引用

     private void print(){
        System.out.println(Thread.currentThread().getName() + "-->" + System.currentTimeMillis() + "ms");
    }
    {
        executorService.submit(this::print);
    }

通過(guò)上面不同的行為傳遞方式,能夠比較直觀的體會(huì)到隨著函數(shù)式特性的引入,行為傳遞少了很多樣板代碼,增加了一絲靈活;可見Lambda表達(dá)式是一種緊湊的、傳遞行為的方式。

2. FunctionalInterface 和 Lambda

Java 函數(shù)式編程,只有兩個(gè)核心概念:

FunctionalInterface(函數(shù)接口)是只有一個(gè)抽象方法的接口,用作 Lambda 表達(dá)式的類型。

Lambda 表達(dá)式,及要傳遞的行為代碼,更像是一個(gè)匿名函數(shù)(當(dāng)然 java 中并沒(méi)有這個(gè)概念),將行為像數(shù)據(jù)那樣進(jìn)行傳遞。

換個(gè)好理解但是不正規(guī)的說(shuō)法,F(xiàn)unctionalInterface 為類型,Lambda 表達(dá)式為值;我們可以將一個(gè) Lambda 表達(dá)式賦予一個(gè)符合 FunctionalInterface 要求的接口變量(局部變量、方法參數(shù))。

2.1. Lambda 表達(dá)式

先看幾個(gè) Lambda 表達(dá)式的例子:

        // 不包含參數(shù),用()表示沒(méi)有參數(shù)
        // 表達(dá)式主體只有一個(gè)語(yǔ)句,可以省略{}
        Runnable helloWord = () -> System.out.println("Hello World");

        // 表達(dá)式主體由多個(gè)語(yǔ)句組成,不能省略{}
        Runnable helloWords = () -> {
            System.out.println("Hello");
            System.out.println("Word");
            System.out.println("Word");
        };

        // 表達(dá)式中只有一個(gè)參數(shù),可以省略()
        Consumer<String> infoConsumer = msg -> System.out.println("Hello " + msg);

        // 表達(dá)式由多個(gè)參數(shù)組成,不可省略()
        BinaryOperator<Integer> add1 = (Integer i ,Integer j) -> i + j;

        // 編譯器會(huì)進(jìn)行類型推斷,在沒(méi)有歧義情況下可以省略類型聲明,但是不可省略()
        BinaryOperator<Integer> add2 = (i, j) -> i + j;

綜上可見,一個(gè) Lambda 表達(dá)式主要由三部分組成:

  1. 參數(shù)列表
  2. 箭頭分隔符(->)
  3. 主體,單個(gè)表達(dá)式或語(yǔ)句塊

我們?cè)谑褂媚涿麅?nèi)部類時(shí)有一些限制:引用方法中的變量時(shí),需要將變量聲明為 final,不能為其進(jìn)行重新賦值,如下:

        final String msg = "World";
        Runnable print = new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello"  + msg);
            }
        };

在 Java8 中放松了這個(gè)限制,可以引用非 final 變量,但是該變量在既成事實(shí)上必須是 final 的,雖然無(wú)需將變量聲明為 final,在 Lambda 表達(dá)式中,也無(wú)法用作非最終態(tài)變量,及只能給該變量賦值一次(與用 final 聲明變量效果相同)。

2.2 FunctionalInterface

FunctionalInterface,只有一個(gè)抽象方法的接口就是函數(shù)式接口,接口中單一方法命名并不重要,只要方法簽名與 Lambda 表達(dá)式的類型匹配即可。

Java 內(nèi)置了常用函數(shù)接口如下:

1. Predicate<T>

參數(shù)類型:T
返回值:boolean
示例:Predicate<String> isAdmin = name -> "admin".equals(name);
2. Consumer<T>

參數(shù):T
返回值:void
示例:Consumer<String> print = msg -> System.out.println(msg);
3. Function<T,R>

參數(shù):T
返回值:R
示例:Function<Long, String> toStr = value -> String.valueOf(value);
4. Supplier<T>

參數(shù):none
返回值:T
示例:Supplier<Date> now = () -> new Date();
5. UnaryOperator<T>

參數(shù):T
返回值:T
示例:UnaryOperator<Boolean> negation = value -> !value.booleanValue();
6. BinaryOperator<T>

參數(shù):(T, T)
返回值:T
示例:BinaryOperator<Integer> intDouble = (i, j) -> i + j;
7. Runnable

參數(shù):none
返回值:void
示例:Runnable helloWord = () -> System.out.println("Hello World");
8. Callable<T>

參數(shù):nont
返回值:T
示例:Callable<Date> now1 = () -> new Date();

當(dāng)然我們也可以根據(jù)需求自定義函數(shù)接口,為了保證接口的有效性,可以在上面添加 @FunctionalInterface 注解,該注解會(huì)強(qiáng)制 javac 檢測(cè)一個(gè)接口是否符合函數(shù)式接口的規(guī)范,例如:

    @FunctionalInterface
    interface CustomFunctionalInterface{
        void print(String msg);
    }
    CustomFunctionalInterface cfi= msg -> System.out.println(msg);

2.3 方法引用

Lambda 表達(dá)式一種常用方法便是直接調(diào)用其他方法,針對(duì)這種情況,Java8 提供了一個(gè)簡(jiǎn)寫語(yǔ)法,及方法引用,用于重用已有方法。

凡是可以使用 Lambda 表達(dá)式的地方,都可以使用方法引用。

方法應(yīng)用的標(biāo)準(zhǔn)語(yǔ)法為 ClassName::methodName,雖然這是一個(gè)方法,但不需要再后面加括號(hào),因?yàn)檫@里并不直接調(diào)用該方法。

    Function<User, String> f1 = user->user.getName();
    Function<User, String> f2 = User::getName;

    Supplier<User> s1 = ()->new User();
    Supplier<User> s2 = User::new;

    Function<Integer, User[]> sa1 = count -> new User[count];
    Function<Integer, User[]> sa2 = User[]::new;

方法引用主要分為如下幾種類型:

  • 靜態(tài)方法引用:className::methodName
  • 實(shí)例方法引用:instanceName::methodName
  • 超類實(shí)體方法引用:supper::mehtodName
  • 構(gòu)造函數(shù)方法引用:className::new
  • 數(shù)組構(gòu)造方法引用:ClassName[]::new

2.4 類型推斷

類型推斷,是 Java7 就引入的目標(biāo)類型推斷的擴(kuò)展,在 Java8 中對(duì)其進(jìn)行了改善,程序員可以省略 Lambda 表達(dá)式中的所有參數(shù)類型,Javac 會(huì)根據(jù) Lambda 表達(dá)式式上下文信息自動(dòng)推斷出參數(shù)的正確類型。

大多數(shù)情況下 javac 能夠準(zhǔn)確的完成類型推斷,但由于 Lambda 表達(dá)式與函數(shù)名無(wú)關(guān),只與方法簽名相關(guān),因此會(huì)出現(xiàn)類型對(duì)推斷失效的情況,這時(shí)可以使用手工類型轉(zhuǎn)換幫助 javac 進(jìn)行正確的判斷。

    // Supplier<String>, Callable<String> 具有相同的方法簽名
    private void print(Supplier<String> stringSupplier){
        System.out.println("Hello " + stringSupplier.get());
    }

    private void print(Callable<String> stringCallable){
        try {
            System.out.println("Hello " + stringCallable.call());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    {
        // Error, 因?yàn)閮蓚€(gè)print同時(shí)滿足需求
        print(()->"World");
        // 使用類型轉(zhuǎn)換,為編譯器提供更多信息
        print((Supplier<String>) ()->"World");

        print((Callable<String>) ()-> "world");

    }

3. 類庫(kù)的升級(jí)改造

Java8 另一個(gè)變化是引入了 默認(rèn)方法 和接口的 靜態(tài)方法 ,自此以后 Java 接口中方法也可以包含代碼體了。

3.1 默認(rèn)方法

默認(rèn)方法允許接口方法定義默認(rèn)實(shí)現(xiàn),而所有子類都將擁有該方法及實(shí)現(xiàn)。使其能夠在不改變子類實(shí)現(xiàn)的情況下(很多時(shí)候我們無(wú)法拿到子類的源碼),為所有子類添加新的功能,從而最大限度的保證二進(jìn)制接口的兼容性。

默認(rèn)方法的另一個(gè)優(yōu)勢(shì)是該方法是可選的,子類可以根據(jù)不同的需求 Override 默認(rèn)實(shí)現(xiàn),為其提供擴(kuò)展性保證。

其中 Collection 中的 forEach,stream 功能都是通過(guò)該技術(shù)統(tǒng)一添加到接口中的。

    // Collection 中的forEache實(shí)現(xiàn)
    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }
    // Collection中的stream實(shí)現(xiàn)
    default Stream<E> stream() {
        return StreamSupport.stream(spliterator(), false);
    }

從上可見,默認(rèn)方法的寫法也是比較簡(jiǎn)單的,只需在方法聲明中添加 defalut 關(guān)鍵字,然后提供方法的默認(rèn)實(shí)現(xiàn)即可。

和類不同,接口中沒(méi)有成員變量,因此默認(rèn)方法只能通過(guò)調(diào)用子類的方法來(lái)修改子類本身,避免了對(duì)子類的實(shí)現(xiàn)做出各種假設(shè)。

3.1.1 默認(rèn)方法與子類

添加默認(rèn)方法特性后,方法的重寫規(guī)則也發(fā)生了變化,具體的場(chǎng)景如下:

a. 沒(méi)有重寫

沒(méi)有重寫是最簡(jiǎn)單的情況,子類調(diào)用該方法的時(shí)候,自然繼承了默認(rèn)方法。

    interface Parent{
        default void welcome(){
            System.out.println("Parent");
        }
    }

    // 調(diào)用Parent中的welcome, 輸入"Parent"
    class ParentNotImpl implements Parent{

    }

b. 子接口重寫

子接口對(duì)父接口中的默認(rèn)方法進(jìn)行了重新,其子類方法被調(diào)用時(shí),執(zhí)行子接口中的默認(rèn)方法

     interface Parent{
        default void welcome(){
            System.out.println("Parent");
        }
    }


    interface ChildInterface extends Parent{
        @Override
        default void welcome(){
            System.out.println("ChildInterface");
        }
    }

    // 執(zhí)行ChildInterface中的welcome, 輸入 "ChildInterface"
    class ChildImpl implements ChildInterface{

    }

c. 類重寫

一旦類中重寫了默認(rèn)方法,優(yōu)先選擇類中定義的方法,如果存在多級(jí)類繼承,遵循類繼承邏輯。

     interface Parent{
        default void welcome(){
            System.out.println("Parent");
        }
    }


    interface ChildInterface extends Parent{
        @Override
        default void welcome(){
            System.out.println("ChildInterface");
        }
    }

    //執(zhí)行子類中的welcome方法,輸出"ChildImpl"
    class ChildImpl1 implements ChildInterface{
        @Override
        public void welcome(){
            System.out.println("ChildImpl");
        }
    }
3.1.2 多重繼承

接口允許多重繼承,因此有可能會(huì)碰到兩個(gè)接口包含簽名相同的默認(rèn)方法的情況,此時(shí) javac 并不明確應(yīng)該繼承哪個(gè)接口中的方法,因此會(huì)導(dǎo)致編譯出錯(cuò),這時(shí)需要在類中實(shí)現(xiàn)該方法,如果想調(diào)用特定父接口中的默認(rèn)方法,可以使用 ParentInterface.super.method() 的方式來(lái)指明具體的接口。

    interface Parent1 {
        default void print(){
            System.out.println("parent1");
        }
    }

    interface Parent2{
        default void print(){
            System.out.println("parent2");
        }
    }

    class Child implements Parent1, Parent2{
        @Override
        public void print() {
            System.out.println("self");
            Parent1.super.print();
            Parent2.super.print();
        }
    }

現(xiàn)在的接口提供了某種形式上的多繼承功能,然而多重繼承存在很多詬病。很多人認(rèn)為多重繼承的問(wèn)題在于對(duì)象狀態(tài)的繼承,而不是代碼塊的繼承,默認(rèn)方法避免了狀態(tài)的繼承,也因此避免了 C++ 中多重繼承最大的缺點(diǎn)。

接口和抽象類之間還是有明顯的區(qū)別。接口允許多重繼承,卻沒(méi)有成員變量;抽象類可以繼承成員變量,卻不能多重繼承。

從某種角度出發(fā),Java 通過(guò)接口默認(rèn)方法實(shí)現(xiàn)了代碼多重繼承,通過(guò)類實(shí)現(xiàn)了狀態(tài)單一繼承。

3.1.3 三定律

如果對(duì)默認(rèn)方法的工作原理,特別是在多重繼承下的行為沒(méi)有把握,可以通過(guò)下面三條簡(jiǎn)單定律幫助大家。

  1. 類勝于方法。
    如果在繼承鏈中有方法體或抽象的方法聲明,那么就可以忽略接口中定義的方法。
  2. 子類勝于父類。
    如果一個(gè)接口繼承另一個(gè)接口,且兩個(gè)接口都定義了一個(gè)默認(rèn)方法,那么子接口中定義的方法勝出。
  3. 沒(méi)有規(guī)則三。
    如果上面兩條規(guī)則不適用,子類要么實(shí)現(xiàn)該方法,要么將該方法聲明為抽象方法。

3.2 接口靜態(tài)方法

人們?cè)诰幊踢^(guò)程中積累了這樣一條經(jīng)驗(yàn),創(chuàng)建一個(gè)包含很多靜態(tài)方法的一個(gè)類。很多時(shí)候類是一個(gè)放置工具方法的好地方,比如 Java7 引入的 Objects 類,就包含很多工具方法,這些方法不是屬于具體的某個(gè)類。

如果一個(gè)方法有充分的語(yǔ)義原因和某個(gè)概念相關(guān),那么就應(yīng)該講該方法和相關(guān)的類或接口放在一起,而不是放到另一個(gè)工具類中,這非常有助于更好的組織代碼。

在接口中定義靜態(tài)方法,只需使用 static 關(guān)鍵字進(jìn)行描述即可,例如 Stream 接口中的 of 方法。

    /**
     * Returns a sequential {@code Stream} containing a single element.
     *
     * @param t the single element
     * @param <T> the type of stream elements
     * @return a singleton sequential stream
     */
    public static<T> Stream<T> of(T t) {
        return StreamSupport.stream(new Streams.StreamBuilderImpl<>(t), false);
    }

3.3 Stream

Stream 是 Java8 中最耀眼的亮點(diǎn),它使得程序員得以站在更高的抽象層次對(duì)集合進(jìn)行操作。

Stream 是用函數(shù)式編程方式在集合類上進(jìn)行復(fù)雜操作的工具。

3.3.1. 從外部迭代到內(nèi)部迭代

Java 程序員使用集合時(shí),一個(gè)通用模式就是在集合上進(jìn)行迭代,然后處理返回的每一個(gè)元素,盡管這種操作可行但存在幾個(gè)問(wèn)題:

  • 大量的樣板代碼
  • 模糊了程序本意
  • 串行化執(zhí)行

常見集合遍歷如下:

     // 常見寫法1,不推薦使用
    public void printAll1(List<String> msg){
        for (int i=0; i< msg.size(); i++){
            String m = msg.get(i);
            System.out.println(m);
        }
    }

    // Java5之前,正確寫法,過(guò)于繁瑣
    public void printAll2(List<String> msg){
        Iterator<String> iterator = msg.iterator();
        while (iterator.hasNext()){
            String m = iterator.next();
            System.out.println(m);
        }
    }

    // Java5之后,加強(qiáng)for循環(huán),采用語(yǔ)法糖,簡(jiǎn)化for循環(huán),內(nèi)部轉(zhuǎn)化為Iterator方式
    public void printAll3(List<String> msg){
        for (String m : msg){
            System.out.println(m);
        }
    }

整個(gè)迭代過(guò)程,通過(guò)顯示的調(diào)用 Iterator 對(duì)象的 hasNext 和 next 方法完成整個(gè)迭代,這成為外部迭代。

外部迭代

另一種方式成為內(nèi)部迭代,及將操作行為作為參數(shù)傳遞給 Stream,在 Stream 內(nèi)部完成迭代操作。

     // Java8中,使用Stream進(jìn)行內(nèi)部迭代操作
    public void printAll4(List<String> msg){
        msg.stream().forEach(System.out::println);
    }

內(nèi)部迭代:

內(nèi)部迭代
3.3.2. 惰性求值 VS 及早求值

Stream 中存在兩類方法,不產(chǎn)生值的方法稱為惰性方法;從 Stream 中產(chǎn)生值的方法叫做及早求值方法。

判斷一個(gè)方法的類別很簡(jiǎn)單:如果返回值是 Stream,那么就是惰性方法;如果返回值是另一個(gè)值或?yàn)榭?,那么就是及早求值方法?/p>

惰性方法返回的 Stream 對(duì)象不是一個(gè)新的集合,而是創(chuàng)建新集合的配方,Stream 本身不會(huì)做任何迭代操作,只有調(diào)用及早求值方法時(shí),才會(huì)開始真正的迭代。

整個(gè)過(guò)程與 Builder 模式有共通之處,惰性方法負(fù)責(zé)對(duì) Stream 進(jìn)行裝配(設(shè)置 builder 的屬性),調(diào)用及早求值方法時(shí)(調(diào)用 builder 的 build 方法),按照之前的裝配信息進(jìn)行迭代操作。

常見 Stream 操作:

3.3.2.1 collect(toList())

及早求值方法:

collect(toList()) 方法由 Stream 里面的值生成一個(gè)列表,是一個(gè)及早求值操作。

collect 的功能不僅限于此,它是一個(gè)非常強(qiáng)大的結(jié)構(gòu)。

     @Data
    class User{
        private String name;
    }
    public List<String> getNames(List<User> users){
        List<String> names = new ArrayList<>();
        for (User user : users){
            names.add(user.getName());
        }
        return names;
    }

    public List<String> getNamesUseStream(List<User> users){
      // 方法引用
      //return users.stream().map(User::getName).collect(toList());
        // lambda表達(dá)式
        return users.stream().map(user -> user.getName()).collect(toList());
    }

3.3.2.2. count、max、min

及早求值方法:

Stream 上最常用的操作之一就是求總數(shù)、最大值和最小值,count、max 和 min 足以解決問(wèn)題。

    public Long getCount(List<User> users){
        return users.stream().filter(user -> user != null).count();
    }
    // 求最小年齡
    public Integer getMinAge(List<User> users){
        return users.stream().map(user -> user.getAge()).min(Integer::compareTo).get();
    }

    // 求最大年齡
    public Integer getMaxAge(List<User> users){
        return users.stream().map(user -> user.getAge()).max(Integer::compareTo).get();
    }

min 和 max 入?yún)⑹且粋€(gè) Comparator 對(duì)象,用于元素之間的比較,返回值是一個(gè) Optional<T>,它代表一個(gè)可能不存在的值,如果 Stream 為空,那么該值不存在,如果不為空,該值存在。通過(guò) get 方法可以獲取 Optional 中的值。

3.3.2.3 findAny、findFirst

及早求值方法:

兩個(gè)函數(shù)都以O(shè)ptional為返回值,用于表示是否找到。

    public Optional<User> getAnyActiveUser(List<User> users){
        return users.stream()
                .filter(user -> user.isActive())
                .findAny();
    }

    public Optional<User> getFirstActiveUser(List<User> users){
        return users.stream()
                .filter(user -> user.isActive())
                .findFirst();
    }

3.3.2.4 allMatch、anyMatch、noneMatch

及早求值方法:

均以 Predicate 作為輸入?yún)?shù),對(duì)集合中的元素進(jìn)行判斷,并返回最終的結(jié)果。

    // 所有用戶是否都已激活
    boolean allMatch = users.stream().allMatch(user -> user.isActive());
    // 是否有激活用戶
    boolean anyMatch = users.stream().anyMatch(user -> user.isActive());
    // 是否所有用戶都沒(méi)有激活
    boolean noneMatch = users.stream().noneMatch(user -> user.isActive());

3.3.2.6. forEach

及早求值:

以 Consumer 為參數(shù),對(duì) Stream 中復(fù)合條件的對(duì)象進(jìn)行操作。

    public void printActiveName(List<User> users){
        users.stream()
                .filter(user -> user.isActive())
                .map(user -> user.getName())
                .forEach(name -> System.out.println(name));
    }

3.3.2.7 reduce

及早求值方法:

reduce 操作可以實(shí)現(xiàn)從一組值中生成一個(gè)值,之前提到的 count、min、max 方法因?yàn)楸容^通用,單獨(dú)提取成方法,事實(shí)上,這些方法都是通過(guò) reduce 完成的。

下圖展示的是對(duì) stream 進(jìn)行求和的過(guò)程,以 0 為起點(diǎn),每一步都將 stream 中的元素累加到 accumulator 中,遍歷至最后一個(gè)元素,accumulator 就是所有元素值的和。

Stream求和過(guò)程

3.3.2.8. filter

惰性求值方法:

以 Predicate 作為參數(shù)(相當(dāng)于 if 語(yǔ)句),對(duì) Stream 中的元素進(jìn)行過(guò)濾,只有復(fù)合條件的元素才能進(jìn)入下面的處理流程。

處理流程如下:

Stream Filter操作
    public List<User> getActiveUser(List<User> users){
        return users.stream()
                .filter(user -> user.isActive())
                .collect(toList());
    }

3.3.2.9 map

及早求值方法:
以 Function 作為參數(shù),將 Stream 中的元素從一種類型轉(zhuǎn)換成另外一種類型。

處理過(guò)程如下:

Stream Map
    public List<String> getNames(List<User> users){
        return users.stream()
                .map(user -> user.getName())
                .collect(toList());
    }

3.3.2.10 peek

Stream 提供的是內(nèi)迭代,有時(shí)候?yàn)榱斯δ苷{(diào)試,需要查看每個(gè)值,同時(shí)能夠繼續(xù)操作流,這時(shí)就會(huì)用到 peek 方法。

    public void printActiveName(List<User> users){
        users.stream()
                .filter(user -> user.isActive())
                .peek(user -> System.out.println(user.isActive()))
                .map(user -> user.getName())
                .forEach(name -> System.out.println(name));
    }

3.3.2.11 其他

針對(duì)集合 Stream 還提供了許多功能強(qiáng)大的操作,暫不一一列舉,簡(jiǎn)單匯總一下。

  • distinct:進(jìn)行去重操作
  • sorted:進(jìn)行排序操作
  • limit:限定結(jié)果輸出數(shù)量
  • skip:跳過(guò) n 個(gè)結(jié)果,從 n+1 開始輸出

3.4 Optional

Java 程序中出現(xiàn)最多的異常就是 NullPointerException,沒(méi)有之一。Optional 的出現(xiàn)力求改變這一狀態(tài)。

Optional 對(duì)象相當(dāng)于值的容器,而該值可以通過(guò) get 方法獲取,同時(shí) Optional 提供了很多函數(shù)用于對(duì)值進(jìn)行操作,從而最大限度的避免 NullPointerException 的出現(xiàn)。

Optional 與 Stream 的用法基本類型,所提供的方法同樣分為惰性和及早求值兩類,惰性方法主要用于流程組裝,及早求值用于最終計(jì)算。

3.4.1 of

使用工廠方法 of,可以從一個(gè)值中創(chuàng)建一個(gè) Optional 對(duì)象,如果值為 null,會(huì)報(bào) NullPointerException。

    Optional<String> dataOptional = Optional.of("a");
    String data = dataOptional.get(); // data is "a"

    Optional<String> dataOptional = Optional.of(null);
    String data = dataOptional.get(); // throw NullPointerException
3.4.2 empty

工廠方法 empty,可以創(chuàng)建一個(gè)不包含任何值的 Optional 對(duì)象。

    Optional<String> dataOptional = Optional.empty();
    String data = dataOptional.get(); //throw NoSuchElementException
3.4.3 ofNullable

工廠方法 ofNullable,可將一個(gè)空值轉(zhuǎn)化成 Optional。

     public static <T> Optional<T> ofNullable(T value) {
        return value == null ? empty() : of(value);
    }
3.4.4 get、orElse、orElseGet、orElseThrow

直接求值方法,用于獲取 Optional 中值,避免空指針異常的出現(xiàn)。

    Optional<String> dataOptional = Optional.of("a");
    dataOptional.get(); // 獲取Optional中的值, 不存在會(huì)拋出NoSuchElementException
    dataOptional.orElse("b"); //獲取Optional中的值,不存在,直接返回"B"
    dataOptional.orElseGet(()-> String.valueOf(System.currentTimeMillis())); //獲取Optional中的值,不存在,對(duì)Supplier進(jìn)行計(jì)算,并返回計(jì)算結(jié)果
    dataOptional.orElseThrow(()-> new XXXException()); //獲取Optional中的值,不存在,拋出自定義異常
3.4.5 isPresent、ifPresent

直接求值方法,isPresent 用于判斷 Optional 中是否有值,ifPresent 接收 Consumer 對(duì)象,當(dāng) Optional 有值的情況下執(zhí)行。

    Optional<String> dataOptional = Optional.of("a");
    String value = null;
    if (dataOptional.isPresent()){
        value = dataOptional.get();
    }else {
        value = "";
    }
    //等價(jià)于
    String value2 = dataOptional.orElse("");
    // 當(dāng)Optional中有值的時(shí)候執(zhí)行
    dataOptional.ifPresent(v->System.out.println(v));
3.4.6 map

惰性求值方法。map 與 Stream 中的用法基本相同,用于對(duì) Optional 中的值進(jìn)行映射處理,從而避免了大量 if 語(yǔ)句嵌套,多個(gè) map 組合成鏈,只需對(duì)最終的結(jié)果進(jìn)行操作,中間過(guò)程中如果存在 null 值,之后的 map 不會(huì)執(zhí)行。

    @Data
    static class Order{
        private Name owner;
    }

    @Data
    static class User{
        private Name name;
    }

    @Data
    static class Name{
        String firstName;
        String midName;
        String lastName;
    }
    private String getFirstName(Order order){
        if (order == null){
            return "";
        }
        if (order.getOwner() == null){
            return "";
        }
        if (order.getOwner().getFirstName() == null){
            return "";
        }
        return order.getOwner().getFirstName();
    }
    private String getFirstName(Optional<Order> orderOptional){
        return orderOptional.map(order -> order.getOwner())
                .map(user->user.getFirstName())
                .orElse("");
    }
3.4.7 filter

惰性求值,對(duì) Optional 中的值進(jìn)行過(guò)濾,如果 Optional 為 empty,直接返回 empty;如果 Optional 中存在值,則對(duì)值進(jìn)行驗(yàn)證,驗(yàn)證通過(guò)返回原 Optional,驗(yàn)證不通過(guò)返回 empty。

     public Optional<T> filter(Predicate<? super T> predicate) {
        Objects.requireNonNull(predicate);
        if (!isPresent())
            return this;
        else
            return predicate.test(value) ? this : empty();
    }

4. Lambda 下模式的進(jìn)化

設(shè)計(jì)模式是人們熟悉的一種設(shè)計(jì)思路,他是軟件架構(gòu)中解決通用問(wèn)題的模板,將解決特定問(wèn)題的最佳實(shí)踐固定下來(lái),但設(shè)計(jì)模式本身會(huì)比較復(fù)雜,包含多個(gè)接口、若干個(gè)實(shí)現(xiàn)類,應(yīng)用過(guò)程相對(duì)繁瑣,這也是影響其應(yīng)用的原因之一。

Lambda 表達(dá)式大大簡(jiǎn)化了 Java 中行為傳遞的問(wèn)題,對(duì)于很多行為式設(shè)計(jì)模式而言,減少了不少構(gòu)建成本。

4.1 命令模式

命令者是一個(gè)對(duì)象,其封裝了調(diào)用另一個(gè)方法的實(shí)現(xiàn)細(xì)節(jié),命令者模式使用該對(duì)象可以編寫根據(jù)運(yùn)行時(shí)條件,順序調(diào)用方法的一般性代碼。

大多數(shù)命令模式中的命令對(duì)象,其實(shí)是一種行為的封裝,甚至是對(duì)其他對(duì)象內(nèi)部行為的一種適配,這種情況下,Lambda 表達(dá)式并有了用武之地。

    interface Command{
        void act();
    }

    interface Editor{
        void open();
        void write(String data);
        void save();
    }

    class CommandRunner{
        private List<Command> commands = new ArrayList<>();

        public void run(Command command){
            command.act();
            this.commands.add(command);
        }

        public void redo(){
            this.commands.forEach(Command::act);
        }
    }

    class OpenCommand implements Command{
        private final Editor editor;

        OpenCommand(Editor editor) {
            this.editor = editor;
        }

        @Override
        public void act() {
            this.editor.open();
        }
    }

    class WriteCommand implements Command{
        private final Editor editor;
        private final String data;
        WriteCommand(Editor editor, String data) {
            this.editor = editor;
            this.data = data;
        }

        @Override
        public void act() {
            editor.write(this.data);
        }
    }

    class SaveCommand implements Command{
        private final Editor editor;

        SaveCommand(Editor editor) {
            this.editor = editor;
        }

        @Override
        public void act() {
            this.editor.save();
        }
    }

    public void useCommand(){
        CommandRunner commandRunner = new CommandRunner();
        Editor editor = new EditorImpl();
        String data1 = "data1";
        String data2 = "data2";
        commandRunner.run(new OpenCommand(editor));
        commandRunner.run(new WriteCommand(editor, data1));
        commandRunner.run(new WriteCommand(editor, data2));
        commandRunner.run(new SaveCommand(editor));
    }

    public void useLambda(){
        CommandRunner commandRunner = new CommandRunner();
        Editor editor = new EditorImpl();
        String data1 = "data1";
        String data2 = "data2";
        commandRunner.run(()->editor.open());
        commandRunner.run(()->editor.write(data1));
        commandRunner.run(()->editor.write(data2));
        commandRunner.run(()->editor.save());
    }



    class EditorImpl implements Editor{

        @Override
        public void open() {

        }

        @Override
        public void write(String data) {

        }

        @Override
        public void save() {

        }
    }

從代碼中可見,Lambda 表達(dá)式的應(yīng)用,減少了創(chuàng)建子類的負(fù)擔(dān),增加了代碼的靈活性。

4.2 策略模式

策略模式能夠在運(yùn)行時(shí)改變軟件的算法行為,其核心的實(shí)現(xiàn)思路是,使用不同的算法來(lái)解決同一個(gè)問(wèn)題,然后將這些算法封裝在一個(gè)統(tǒng)一的接口背后。

可見策略模式也是一種行為行為傳遞的模式。

策略模式下的壓縮算法
    interface CompressionStrategy{
        OutputStream compress(OutputStream outputStream) throws IOException;
    }

    class GzipBasedCompressionStrategy implements CompressionStrategy{

        @Override
        public OutputStream compress(OutputStream outputStream) throws IOException {
            return new GZIPOutputStream(outputStream);
        }
    }

    class ZipBasedCompressionStrategy implements CompressionStrategy{

        @Override
        public OutputStream compress(OutputStream outputStream) throws IOException {
            return new ZipOutputStream(outputStream);
        }
    }

    class Compressor{
        private final CompressionStrategy compressionStrategy;

        Compressor(CompressionStrategy compressionStrategy) {
            this.compressionStrategy = compressionStrategy;
        }

        public void compress(Path inFile, File outFile) throws IOException {
            try (OutputStream outputStream = new FileOutputStream(outFile)){
                Files.copy(inFile, this.compressionStrategy.compress(outputStream));
            }
        }
    }

    {
        Compressor gzipCompressor = new Compressor(new GzipBasedCompressionStrategy());
        gzipCompressor.compress(in,out);

        Compressor ziCompressor = new Compressor(new ZipBasedCompressionStrategy());
        ziCompressor.compress(in,out);
    }

    {
        Compressor gzipCompressor = new Compressor(GZIPOutputStream::new);
        gzipCompressor.compress(in,out);

        Compressor ziCompressor = new Compressor(ZipOutputStream::new);
        ziCompressor.compress(in,out);
    }

4.3 觀察者模式

觀察者模式中,被觀察者持有觀察者的一個(gè)列表,當(dāng)被觀察者的狀態(tài)發(fā)送變化時(shí),會(huì)通知觀察者。

對(duì)于一個(gè)觀察者來(lái)說(shuō),往往是對(duì)一個(gè)行為的封裝。

    interface NameObserver{
        void onNameChange(String oName, String nName);
    }

    @Data
    class User {
        private final List<NameObserver> nameObservers = new ArrayList<>();
        @Setter(AccessLevel.PRIVATE)
        private String name;

        public void updateName(String nName){
            String oName = getName();
            setName(nName);
            nameObservers.forEach(nameObserver -> nameObserver.onNameChange(oName, nName));
        }

        public void addObserver(NameObserver nameObserver){
            this.nameObservers.add(nameObserver);
        }
    }

    class LoggerNameObserver implements NameObserver{

        @Override
        public void onNameChange(String oName, String nName) {
            System.out.println(String.format("old Name is %s, new Name is %s", oName, nName));
        }
    }

    class NameChangeNoticeObserver implements NameObserver{

        @Override
        public void onNameChange(String oName, String nName) {
            notic.send(String.format("old Name is %s, new Name is %s", oName, nName));
        }
    }

    {
        User user = new User();
        user.addObserver(new LoggerNameObserver());
        user.addObserver(new NameChangeNoticeObserver());
        user.updateName("張三");
    }

    {
        User user = new User();
        user.addObserver((oName, nName) ->
                System.out.println(String.format("old Name is %s, new Name is %s", oName, nName)));
        user.addObserver((oName, nName) ->
                notic.send(String.format("old Name is %s, new Name is %s", oName, nName)));
        user.updateName("張三");
    }

4.4 模板方法模式

模板方法將整體算法設(shè)計(jì)成一個(gè)抽象類,他有一系列的抽象方法,代表方法中可被定制的步驟,同時(shí)這個(gè)類中包含一些通用代碼,算法的每一個(gè)變種都由具體的類實(shí)現(xiàn),他們重新抽象方法,提供相應(yīng)的實(shí)現(xiàn)。

模板方法,實(shí)際是行為的一種整合,內(nèi)部大量用到行為的傳遞。
先看一個(gè)標(biāo)準(zhǔn)的模板方法:

    interface UserChecker{
        void check(User user);
    }

    abstract class AbstractUserChecker implements UserChecker{
        @Override
        public final void check(User user){
            checkName(user);
            checkAge(user);
        }
        abstract void checkName(User user);

        abstract void checkAge(User user);
    }

    class SimpleUserChecker extends AbstractUserChecker {

        @Override
        void checkName(User user) {
            Preconditions.checkArgument(StringUtils.isNotEmpty(user.getName()));
        }

        @Override
        void checkAge(User user) {
            Preconditions.checkArgument(user.getAge() != null);
            Preconditions.checkArgument(user.getAge().intValue() > 0);
            Preconditions.checkArgument(user.getAge().intValue() < 150);
        }
    }

    {
        UserChecker userChecker = new SimpleUserChecker();
        userChecker.check(new User());
    }

    class LambdaBaseUserChecker implements UserChecker{
        private final List<Consumer<User>> userCheckers = Lists.newArrayList();
        public LambdaBaseUserChecker(List<Consumer<User>>userCheckers){
            this.userCheckers.addAll(userCheckers);
        }

        @Override
        public void check(User user){
            this.userCheckers.forEach(userConsumer -> userConsumer.accept(user));
        }
    }

    {
        UserChecker userChecker = new LambdaBaseUserChecker(Arrays.asList(
                user -> Preconditions.checkArgument(StringUtils.isNotEmpty(user.getName())),
                user -> Preconditions.checkArgument(user.getAge() != null),
                user -> Preconditions.checkArgument(user.getAge().intValue() > 0),
                user -> Preconditions.checkArgument(user.getAge().intValue() < 150)
        ));

        userChecker.check(new User());
    }

    @Data
    class User{
        private String name;
        private Integer age;
    }

在看一個(gè) Spring JdbcTemplate,如果使用 Lambda 進(jìn)行簡(jiǎn)化:

    public JdbcTemplate jdbcTemplate;

    public User getUserById(Integer id){
        return jdbcTemplate.query("select id, name, age from tb_user where id = ?", new PreparedStatementSetter() {
            @Override
            public void setValues(PreparedStatement preparedStatement) throws SQLException {
                preparedStatement.setInt(1, id);
            }
        }, new ResultSetExtractor<User>() {
            @Override
            public User extractData(ResultSet resultSet) throws SQLException, DataAccessException {
                User user = new User();
                user.setId(resultSet.getInt("id"));
                user.setName(resultSet.getString("name"));
                user.setAge(resultSet.getInt("age"));
                return user;
            }
        });
    }

    public User getUserByIdLambda(Integer id){
        return jdbcTemplate.query("select id, name, age from tb_user where id = ?",
                preparedStatement -> preparedStatement.setInt(1, id),
                resultSet -> {
                    User user = new User();
                    user.setId(resultSet.getInt("id"));
                    user.setName(resultSet.getString("name"));
                    user.setAge(resultSet.getInt("age"));
                    return user;
                });
    }

    @Data
    class User {
        private Integer id;
        private String name;
        private Integer age;
    }

5. Lambda 下并發(fā)程序

并發(fā)與并行:

  • 并發(fā)是兩個(gè)任務(wù)共享時(shí)間段,并行是兩個(gè)任務(wù)同一時(shí)間發(fā)生。
  • 并行化是指為了縮短任務(wù)執(zhí)行的時(shí)間,將任務(wù)分解為幾個(gè)部分,然后并行執(zhí)行,這和順序執(zhí)行的工作量是一樣的,區(qū)別是多個(gè) CPU 一起來(lái)干活,花費(fèi)的時(shí)間自然減少了。
  • 數(shù)據(jù)并行化。數(shù)據(jù)并行化是指將數(shù)據(jù)分為塊,為每塊數(shù)據(jù)分配獨(dú)立的處理單元。
并行和并發(fā)的區(qū)別

5.1 并行化流操作

并行化流操作是 Stream 提供的一個(gè)特性,只需改變一個(gè)方法調(diào)用,就可以讓其擁有并行操作的能力。

如果已經(jīng)存在一個(gè) Stream 對(duì)象,調(diào)用他的 parallel 方法就能讓其并行執(zhí)行。

如果已經(jīng)存在一個(gè)集合,調(diào)用 parallelStream 方法就能獲取一個(gè)擁有并行執(zhí)行能力的 Stream。

并行流主要解決如何高效使用多核 CPU 的事情。

    @Data
    class Account{
        private String name;
        private boolean active;
        private Integer amount;
    }

    public int getActiveAmount(List<Account> accounts){
        return accounts.parallelStream()
                .filter(account -> account.isActive())
                .mapToInt(account -> account.getAmount())
                .sum();
    }

    public int getActiveAmount2(List<Account> accounts){
        return accounts.stream()
                .parallel()
                .filter(account -> account.isActive())
                .mapToInt(Account::getAmount)
                .sum();
    }

并行流底層使用 fork/join 框架,fork 遞歸式的分解問(wèn)題,然后每個(gè)段并行執(zhí)行,最終有 join 合并結(jié)果,返回最后的值。

Fork/Join分解合并問(wèn)題

5.2 阻塞 IO VS 非阻塞 IO

BIO VS NIO

BIO 阻塞式 IO,是一種通用且容易理解的方式,與程序交互時(shí)通常都符合這種順序執(zhí)行的方式,但其主要的缺陷在于每個(gè) socket 會(huì)綁定一個(gè) Thread 進(jìn)行操作,當(dāng)長(zhǎng)鏈過(guò)多時(shí)會(huì)消耗大量的 Server 資源,從而導(dǎo)致其擴(kuò)展性性下降。

NIO 非阻塞 IO,一般指的是 IO 多路復(fù)用,可以使用一個(gè)線程同時(shí)對(duì)多個(gè) socket 的讀寫進(jìn)行監(jiān)控,從而使用少量線程服務(wù)于大量 Socket。

由于客戶端開發(fā)的簡(jiǎn)便性,大多數(shù)的驅(qū)動(dòng)都是基于 BIO 實(shí)現(xiàn),包括 MySQL、Redis、Mongo 等;在服務(wù)器端,由于其高性能的要求,基本上是 NIO 的天下,以最大限度的提升系統(tǒng)的可擴(kuò)展性。

由于客戶端存在大量的 BIO 操作,我們的客戶端線程會(huì)不停的被 BIO 阻塞,以等待操作返回值,因此線程的效率會(huì)大打折扣。

BIO Client

如上圖,線程在 IO 與 CPU 之間不停切換,走走停停,同時(shí)線程也沒(méi)有辦法釋放,一直等到任務(wù)完成。

5.3 Future

構(gòu)建并發(fā)操作的另一種方案便是 Future,F(xiàn)uture 是一種憑證,調(diào)用方法不是直接返回值,而是返回一個(gè) Future 對(duì)象,剛創(chuàng)建的 Future 為一個(gè)空對(duì)象,由后臺(tái)線程執(zhí)行耗時(shí)操作,并在結(jié)束時(shí)將結(jié)果寫回到 Future 中。

當(dāng)調(diào)用 Future 對(duì)象的 get 方法獲取值時(shí),會(huì)有兩個(gè)可能,如果后臺(tái)線程已經(jīng)運(yùn)行完成,則直接返回;如果后臺(tái)線程沒(méi)有運(yùn)行完成,則阻塞調(diào)用線程,知道后臺(tái)線程運(yùn)行完成或超時(shí)。

使用 Future 方式,可以以并行的方式運(yùn)行多個(gè)子任務(wù)。

當(dāng)主線程需要調(diào)用比較耗時(shí)的操作時(shí),可以將其放在輔助線程中執(zhí)行,并在需要數(shù)據(jù)的時(shí)候從 future 中獲取,如果輔助線程已經(jīng)運(yùn)行完成,則立即拿到返回的結(jié)果,如果輔助線程還沒(méi)有運(yùn)行完成,則主線程等待,并在完成時(shí)獲取結(jié)果。

基于Future的并發(fā)操作

一種常見的場(chǎng)景是在Controller中從多個(gè)Service中獲取結(jié)果,并將其封裝成一個(gè)View對(duì)象返回給前端用于顯示,假設(shè)需要從三個(gè)接口中獲取結(jié)果,每個(gè)接口的平均響應(yīng)時(shí)間是20ms,那按照串行模式,總耗時(shí)為sum(i1, i2, i3) = 60ms;如果按照Future并發(fā)模式將加載任務(wù)交由輔助線程處理,總耗時(shí)為max(i1, i2, i3 ) = 20ms, 大大減少了系統(tǒng)的響應(yīng)時(shí)間。

     private ExecutorService executorService = Executors.newFixedThreadPool(20);

    private User loadUserByUid(Long uid){
       sleep(20);
       return new User();
    }

    private Address loadAddressByUid(Long uid){
        sleep(20);
        return new Address();

    }

    private Account loadAccountByUid(Long uid){
        sleep(20);
        return new Account();
    }

    /**
     * 總耗時(shí) sum(LoadUser, LoadAddress, LoadAccount) = 60ms
     * @param uid
     * @return
     */
    public View getViewByUid1(Long uid){
        User user = loadUserByUid(uid);
        Address address = loadAddressByUid(uid);
        Account account = loadAccountByUid(uid);
        View view = new View();
        view.setUser(user);
        view.setAddress(address);
        view.setAccount(account);
        return view;
    }

    /**
     * 總耗時(shí) max(LoadUser, LoadAddress, LoadAccount) = 20ms
     * @param uid
     * @return
     * @throws ExecutionException
     * @throws InterruptedException
     */
    public View getViewByUid(Long uid) throws ExecutionException, InterruptedException {
        Future<User> userFuture = executorService.submit(()->loadUserByUid(uid));
        Future<Address> addressFuture = executorService.submit(()->loadAddressByUid(uid));
        Future<Account> accountFuture = executorService.submit(()->loadAccountByUid(uid));
        View view = new View();
        view.setUser(userFuture.get());
        view.setAddress(addressFuture.get());
        view.setAccount(accountFuture.get());
        return view;
    }



    private void sleep(long time){
        try {
            TimeUnit.MILLISECONDS.sleep(time);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Data
    class View{
        private User user;
        private Address address;
        private Account account;
    }

    class User{

    }

    class Address{

    }

    class Account{

    }

Future 方式存在一個(gè)問(wèn)題,及在調(diào)用 get 方法時(shí)會(huì)阻塞主線程,這是資源的極大浪費(fèi),我們真正需要的是一種不必調(diào)用 get 方法阻塞當(dāng)前線程,就可以操作 future 對(duì)象返回的結(jié)果。

上例中只是子任務(wù)能夠拆分并能并行執(zhí)行的一種典型案例,在實(shí)際開發(fā)過(guò)程中,我們會(huì)遇到更多、更復(fù)雜的場(chǎng)景,比如:

  • 將兩個(gè) Future 結(jié)果合并成一個(gè),同時(shí)第二個(gè)又依賴于第一個(gè)的結(jié)果
  • 等待 Future 集合中所有記錄的完成
  • 等待 Future 集合中的最快的任務(wù)完成
  • 定義任務(wù)完成后的操作

對(duì)此,我們引入了 CompletableFuture 對(duì)象。

5.4 CompletableFuture

  • CompletableFuture 結(jié)合了 Future 和回調(diào)兩種策略,以更好的處理事件驅(qū)動(dòng)任務(wù)。
  • CompletableFuture 與Stream 的設(shè)計(jì)思路一致,通過(guò)注冊(cè) Lambda 表達(dá)式,把高階函數(shù)鏈接起來(lái),從而定制更復(fù)雜的處理流程。

CompletableFuture 提供了一組函數(shù)用于定義流程,其中包括:

5.4.1 創(chuàng)建函數(shù)

CompletableFuture 提供了一組靜態(tài)方法用于創(chuàng)建 CompletableFuture 實(shí)例:

public static <U> CompletableFuture<U> completedFuture(U value)// :使用已經(jīng)創(chuàng)建好的值,創(chuàng)建 CompletableFuture 對(duì)象。

public static CompletableFuture<Void>   runAsync(Runnable runnable)// 基于 Runnable 創(chuàng)建 CompletableFuture 對(duì)象,返回值為 Void,及沒(méi)有返回值

public static CompletableFuture<Void>   runAsync(Runnable runnable, Executor executor)// 基于 Runnable 和自定義線程池創(chuàng)建 CompletableFuture 對(duì)象,返回值為 Void,及沒(méi)有返回值

public static <U> CompletableFuture<U>  supplyAsync(Supplier<U> supplier)// 基于 Supplier 創(chuàng)建 CompletableFuture 對(duì)象,返回值為 U

public static <U> CompletableFuture<U>  supplyAsync(Supplier<U> supplier, Executor executor)   // 基于 Supplier 和自定義線程池創(chuàng)建 CompletableFuture 對(duì)象,返回值為 U

以 Async 結(jié)尾并且沒(méi)有指定 Executor 的方法會(huì)使用 ForkJoinPool.commonPool() 作為它的線程池執(zhí)行異步代碼。

方法的參數(shù)類型都是函數(shù)式接口,所以可以使用 Lambda 表達(dá)式實(shí)現(xiàn)異步任務(wù)。

5.4.2 計(jì)算結(jié)果完成后

當(dāng) CompletableFuture 計(jì)算完成或者計(jì)算過(guò)程中拋出異常時(shí)進(jìn)行回調(diào)。

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)

Action 的類型是 BiConsumer<? super T,? super Throwable> 它可以處理正常的計(jì)算結(jié)果,或者異常情況。

方法不以 Async 結(jié)尾,意味著 Action 使用相同的線程執(zhí)行,而 Async 可能會(huì)使用其他線程執(zhí)行(如果是使用相同的線程池,也可能會(huì)被同一個(gè)線程選中執(zhí)行)。

exceptionally 針對(duì)異常情況進(jìn)行處理,當(dāng)原始的 CompletableFuture 拋出異常的時(shí)候,就會(huì)觸發(fā)這個(gè) CompletableFuture 的計(jì)算。

下面一組方法雖然也返回 CompletableFuture 對(duì)象,但是對(duì)象的值和原來(lái)的 CompletableFuture 計(jì)算的值不同。當(dāng)原先的 CompletableFuture 的值計(jì)算完成或者拋出異常的時(shí)候,會(huì)觸發(fā)這個(gè) CompletableFuture 對(duì)象的計(jì)算,結(jié)果由 BiFunction 參數(shù)計(jì)算而得。因此這組方法兼有 whenComplete 和轉(zhuǎn)換的兩個(gè)功能。

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)
5.4.3 轉(zhuǎn)化函數(shù)

轉(zhuǎn)化函數(shù)類似于 Stream 中的惰性求助函數(shù),主要對(duì) CompletableFuture 的中間結(jié)果進(jìn)行流程定制。

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)

通過(guò)函數(shù)完成對(duì) CompletableFuture 中的值得轉(zhuǎn)化,Async 在線的線程池中處理,Executor 可以自定義線程池。

5.4.4 純消費(fèi)函數(shù)

上面的方法當(dāng)計(jì)算完成的時(shí)候,會(huì)生成新的計(jì)算結(jié)果 (thenApply, handle),或者返回同樣的計(jì)算結(jié)果 whenComplete,CompletableFuture 還提供了一種處理結(jié)果的方法,只對(duì)結(jié)果執(zhí)行 Action,而不返回新的計(jì)算值,因此計(jì)算值為 Void。

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)

其他的參數(shù)類型與之前的含義一致,不同的是函數(shù)接口 Consumer,這個(gè)接口只有輸入,沒(méi)有返回值。

thenAcceptBoth 以及相關(guān)方法提供了類似的功能,當(dāng)兩個(gè) CompletionStage 都正常完成計(jì)算的時(shí)候,就會(huì)執(zhí)行提供的 action,它用來(lái)組合另外一個(gè)異步的結(jié)果。

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) 
   
public <U>
   CompletableFuture<Void> thenAcceptBothAsync(CompletionStage<? extends
   U> other, BiConsumer<? super T,? super U> action, Executor executor)
5.4.5. 組合函數(shù)

組合函數(shù)主要應(yīng)用于后續(xù)計(jì)算需要 CompletableFuture 計(jì)算結(jié)果的場(chǎng)景。

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

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

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

這一組方法接受一個(gè) Function 作為參數(shù),這個(gè) Function 的輸入是當(dāng)前的 CompletableFuture 的計(jì)算值,返回結(jié)果將是一個(gè)新的 CompletableFuture,這個(gè)新的 CompletableFuture 會(huì)組合原來(lái)的 CompletableFuture 和函數(shù)返回的 CompletableFuture。因此它的功能類似:

A +–> B +—> C 

下面的一組方法 thenCombine 用來(lái)復(fù)合另外一個(gè) CompletionStage 的結(jié)果。兩個(gè) CompletionStage 是并行執(zhí)行的,它們之間并沒(méi)有先后依賴順序,other 并不會(huì)等待先前的 CompletableFuture 執(zhí)行完畢后再執(zhí)行,當(dāng)兩個(gè) CompletionStage 全部執(zhí)行完成后,統(tǒng)一調(diào)用 BiFunction 函數(shù),計(jì)算最終的結(jié)果。

public <U,V> CompletableFuture<V> thenCombine(CompletionStage<?
   extends U> other, BiFunction<? super T,? super U,? extends V> fn)
public <U,V> CompletableFuture<V> thenCombineAsync(CompletionStage<?
   extends U> other, BiFunction<? super T,? super U,? extends V> fn)
public <U,V> CompletableFuture<V> thenCombineAsync(CompletionStage<?
   extends U> other, BiFunction<? super T,? super U,? extends V> fn,
   Executor executor)
5.4.6. Either

Either 系列方法不會(huì)等兩個(gè) CompletableFuture 都計(jì)算完成后執(zhí)行計(jì)算,而是當(dāng)任意一個(gè) CompletableFuture 計(jì)算完成的時(shí)候就會(huì)執(zhí)行。

public CompletableFuture<Void> acceptEither(CompletionStage<? extends T> other, Consumer<? super T> action)

public CompletableFuture<Void> acceptEitherAsync(CompletionStage<? extends T> other, Consumer<? super T> action)

public CompletableFuture<Void> acceptEitherAsync(CompletionStage<? extends T> other, Consumer<? super T> action, Executor executor)

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

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

public <U> CompletableFuture<U> applyToEitherAsync(CompletionStage<? extends T> other, Function<? super T,U> fn, Executor executor)
5.4.7 輔助方法

輔助方法主要指 allOf 和 anyOf,這兩個(gè)靜態(tài)方法用于組合多個(gè) CompletableFuture。

public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs)// allOf方法是當(dāng)所有的CompletableFuture都執(zhí)行完后執(zhí)行計(jì)算。

public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs)// anyOf方法是當(dāng)任意一個(gè)CompletableFuture執(zhí)行完后就會(huì)執(zhí)行計(jì)算。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容