天天看點

Java Stream流、方法引用Stream流、方法引用第一章 Stream流第二章 方法引用

Stream流、方法引用

第一章 Stream流

說到Stream便容易想到I/O Stream,而實際上,誰規定“流”就一定是“IO流”呢?在Java 8中,得益于Lambda所帶 來的函數式程式設計,引入了一個全新的Stream概念,用于解決已有集合類庫既有的弊端。

1.1 引言

傳統集合的多步周遊代碼

幾乎所有的集合(如 Collection 接口或 Map 接口等)都支援直接或間接的周遊操作。而當我們需要對集合中的元 素進行操作的時候,除了必需的添加、删除、擷取外,典型的就是集合周遊。例如:

Java Stream流、方法引用Stream流、方法引用第一章 Stream流第二章 方法引用

這是一段非常簡單的集合周遊操作:對集合中的每一個字元串都進行列印輸出操作。

循環周遊的弊端

Java 8的Lambda讓我們可以更加專注于做什麼(What),而不是怎麼做(How),這點此前已經結合内部類進行 了對比說明。現在,我們仔細體會一下上例代碼,可以發現:

  • for循環的文法就是“怎麼做”
  • for循環的循環體才是“做什麼”

為什麼使用循環?因為要進行周遊。但循環是周遊的唯一方式嗎?周遊是指每一個元素逐一進行處理,而并不是從 第一個到最後一個順次處理的循環。前者是目的,後者是方式。

試想一下,如果希望對集合中的元素進行篩選過濾:

  1. 将集合A根據條件一過濾為子集B;
  2. 然後再根據條件二過濾為子集C。

    那怎麼辦?在Java 8之前的做法可能為:

import java.util.ArrayList;
import java.util.List;

/*
    使用傳統的方式,周遊集合,對集合中的資料進行過濾
 */
public class Demo01List {
    public static void main(String[] args) {
        //建立一個List集合,存儲姓名
        List<String> list = new ArrayList<>();
        list.add("張無忌");
        list.add("周芷若");
        list.add("趙敏");
        list.add("張強");
        list.add("張三豐");

        //對list集合中的元素進行過濾,隻要以張開頭的元素,存儲到一個新的集合中
        List<String> listA = new ArrayList<>();
        for(String s : list){
            if(s.startsWith("張")){
                listA.add(s);
            }
        }

        //對listA集合進行過濾,隻要姓名長度為3的人,存儲到一個新集合中
        List<String> listB = new ArrayList<>();
        for (String s : listA) {
            if(s.length()==3){
                listB.add(s);
            }
        }

        //周遊listB集合
        for (String s : listB) {
            System.out.println(s);
        }
    }
}

           
在這裡插入代碼片
           

這段代碼中含有三個循環,每一個作用不同:

  • 首先篩選所有姓張的人;
  • 然後篩選名字有三個字的人;
  • 後進行對結果進行列印輸出。

每當我們需要對集合中的元素進行操作的時候,總是需要進行循環、循環、再循環。這是理所當然的麼?不是。循 環是做事情的方式,而不是目的。另一方面,使用線性循環就意味着隻能周遊一次。如果希望再次周遊,隻能再使 用另一個循環從頭開始。

那,Lambda的衍生物Stream能帶來怎樣更加優雅的寫法呢?

Stream的更優寫法

import java.util.ArrayList;
import java.util.List;

/*
    使用Stream流的方式,周遊集合,對集合中的資料進行過濾
    Stream流是JDK1.8之後出現的
    關注的是做什麼,而不是怎麼做
 */
public class Demo02Stream {
    public static void main(String[] args) {
        //建立一個List集合,存儲姓名
        List<String> list = new ArrayList<>();
        list.add("張無忌");
        list.add("周芷若");
        list.add("趙敏");
        list.add("張強");
        list.add("張三豐");

        //對list集合中的元素進行過濾,隻要以張開頭的元素,存儲到一個新的集合中
        //對listA集合進行過濾,隻要姓名長度為3的人,存儲到一個新集合中
        //周遊listB集合
        list.stream()
                .filter(name->name.startsWith("張"))
            .filter(name->name.length()==3)
            .forEach(name-> System.out.println(name));
}
}

           

直接閱讀代碼的字面意思即可完美展示無關邏輯方式的語義:擷取流、過濾姓張、過濾長度為3、逐一列印。代碼 中并沒有展現使用線性循環或是其他任何算法進行周遊,我們真正要做的事情内容被更好地展現在代碼中。

1.2 流式思想概述

注意:請暫時忘記對傳統IO流的固有印象!

整體來看,流式思想類似于工廠工廠中的房間的“生産流水線”。

當需要對多個元素進行操作(特别是多步操作)的時候,考慮到性能及便利性,我們應該首先拼好一個“模型”步驟 方案,然後再按照方案去執行它。

Java Stream流、方法引用Stream流、方法引用第一章 Stream流第二章 方法引用

這張圖中展示了過濾、映射、跳過、計數等多步操作,這是一種集合元素的處理方案,而方案就是一種“函數模 型”。圖中的每一個方框都是一個“流”,調用指定的方法,可以從一個流模型轉換為另一個流模型。而右側的數字 3是終結果。

這裡的 filter 、 map 、 skip 都是在對函數模型進行操作,集合元素并沒有真正被處理。隻有當終結方法 count 執行的時候,整個模型才會按照指定政策執行操作。而這得益于Lambda的延遲執行特性。

備注:“Stream流”其實是一個集合元素的函數模型,它并不是集合,也不是資料結構,其本身并不存儲任何 元素(或其位址值)。

Stream(流)是一個來自資料源的元素隊列

  • 元素是特定類型的對象,形成一個隊列。 Java中的Stream并不會存儲元素,而是按需計算.
  • 資料源 流的來源。 可以是集合,數組 等。

和以前的Collection操作不同, Stream操作還有兩個基礎的特征:

  • Pipelining: 中間操作都會傳回流對象本身。 這樣多個操作可以串聯成一個管道, 如同流式風格(fluent style)。 這樣做可以對操作進行優化, 比如延遲執行(laziness)和短路( short-circuiting)。
  • 内部疊代: 以前對集合周遊都是通過Iterator或者增強for的方式, 顯式的在集合外部進行疊代, 這叫做外部疊 代。 Stream提供了内部疊代的方式,流可以直接調用周遊方法。

當使用一個流的時候,通常包括三個基本步驟:擷取一個資料源(source)→ 資料轉換→執行操作擷取想要的結 果,每次轉換原有 Stream 對象不改變,傳回一個新的 Stream 對象(可以有多次轉換),這就允許對其操作可以 像鍊條一樣排列,變成一個管道。

1.3 擷取流

java.util.stream.Stream<T>

是Java 8新加入的常用的流接口。(這并不是一個函數式接口。)

擷取一個流非常簡單,有以下幾種常用的方式:

  • 所有的 Collection 集合都可以通過 stream 預設方法擷取流;
  • Stream 接口的靜态方法 of 可以擷取數組對應的流。

根據Collection擷取流

首先,

java.util.Collection

接口中加入了default方法 stream 用來擷取流,是以其所有實作類均可擷取流。

根據Map擷取流

java.util.Map 接口不是 Collection 的子接口,且其K-V資料結構不符合流元素的單一特征,是以擷取對應的流 需要分key、value或entry等情況:

根據數組擷取流

如果使用的不是集合或映射而是數組,由于數組對象不可能添加預設方法,是以 Stream 接口中提供了靜态方法 of ,使用很簡單:

import java.util.*;
import java.util.stream.Stream;

/*
    java.util.stream.Stream<T>是Java 8新加入的最常用的流接口。(這并不是一個函數式接口。)
    擷取一個流非常簡單,有以下幾種常用的方式:
        - 所有的Collection集合都可以通過stream預設方法擷取流;
            default Stream<E> stream​()
        - Stream接口的靜态方法of可以擷取數組對應的流。
            static <T> Stream<T> of​(T... values)
            參數是一個可變參數,那麼我們就可以傳遞一個數組
 */
public class Demo01GetStream {
    public static void main(String[] args) {
        //把集合轉換為Stream流
        List<String> list = new ArrayList<>();
        Stream<String> stream1 = list.stream();

        Set<String> set = new HashSet<>();
        Stream<String> stream2 = set.stream();

        Map<String,String> map = new HashMap<>();
        //擷取鍵,存儲到一個Set集合中
        Set<String> keySet = map.keySet();
        Stream<String> stream3 = keySet.stream();

        //擷取值,存儲到一個Collection集合中
        Collection<String> values = map.values();
        Stream<String> stream4 = values.stream();

        //擷取鍵值對(鍵與值的映射關系 entrySet)
        Set<Map.Entry<String, String>> entries = map.entrySet();
        Stream<Map.Entry<String, String>> stream5 = entries.stream();

        //把數組轉換為Stream流
        Stream<Integer> stream6 = Stream.of(1, 2, 3, 4, 5);
        //可變參數可以傳遞數組
        Integer[] arr = {1,2,3,4,5};
        Stream<Integer> stream7 = Stream.of(arr);
        String[] arr2 = {"a","bb","ccc"};
        Stream<String> stream8 = Stream.of(arr2);
    }
}

           
備注: of 方法的參數其實是一個可變參數,是以支援數組。

1.4 常用方法

流模型的操作很豐富,這裡介紹一些常用的API。這些方法可以被分成兩種:

  • 延遲方法:傳回值類型仍然是 Stream 接口自身類型的方法,是以支援鍊式調用。(除了終結方法外,其餘方 法均為延遲方法。)
  • 終結方法:傳回值類型不再是 Stream 接口自身類型的方法,是以不再支援類似 StringBuilder 那樣的鍊式調 用。本小節中,終結方法包括 count 和 forEach 方法。
備注:本小節之外的更多方法,請自行參考API文檔。
Java Stream流、方法引用Stream流、方法引用第一章 Stream流第二章 方法引用

Stream流中的常用方法_forEach

void forEach(Consumer<? super T> action);

該方法接收一個Consumer接口函數,會将每一個流元素交給該函數進行處理。

Consumer接口是一個消費型的函數式接口,可以傳遞Lambda表達式,消費資料

簡單記:

forEach方法,用來周遊流中的資料

是一個終結方法,周遊之後就不能繼續調用Stream流中的其他方法

import java.util.stream.Stream;

public class Demo02Stream_forEach {
    public static void main(String[] args) {
        //擷取一個Stream流
        Stream<String> stream = Stream.of("張三", "李四", "王五", "趙六", "田七");
        //使用Stream流中的方法forEach對Stream流中的資料進行周遊
        /*stream.forEach((String name)->{
            System.out.println(name);
        });*/

        stream.forEach(name->System.out.println(name));
    }
}

           

Stream流中的常用方法_filter:

用于對Stream流中的資料進行過濾

Stream<T> filter(Predicate<? super T> predicate);

filter方法的參數Predicate是一個函數式接口,是以可以傳遞Lambda表達式,對資料進行過濾

Predicate中的抽象方法:

boolean test(T t);

Stream流屬于管道流,隻能被消費(使用)一次
        第一個Stream流調用完畢方法,資料就會流轉到下一個Stream上
        而這時第一個Stream流已經使用完畢,就會關閉了
        是以第一個Stream流就不能再調用方法了
           
import java.util.stream.Stream;

public class Demo03Stream_filter {
    public static void main(String[] args) {
        //建立一個Stream流
        Stream<String> stream = Stream.of("張三豐", "張翠山", "趙敏", "周芷若", "張無忌");
        //對Stream流中的元素進行過濾,隻要姓張的人
        Stream<String> stream2 = stream.filter((String name)->{return name.startsWith("張");});
        //周遊stream2流
        stream2.forEach(name-> System.out.println(name));

        /*
            IllegalStateException: stream has already been operated upon or closed
         */
        //周遊stream流
        stream.forEach(name-> System.out.println(name));
    }
}

           

Stream流中的常用方法_map(映射):

用于類型轉換

如果需要将流中的元素映射到另一個流中,可以使用map方法.
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
該接口需要一個Function函數式接口參數,可以将目前流中的T類型資料轉換為另一種R類型的流。
Function中的抽象方法:
    R apply(T t);
           
import java.util.stream.Stream;

public class Demo04Stream_map {
    public static void main(String[] args) {
        //擷取一個String類型的Stream流
        Stream<String> stream = Stream.of("1", "2", "3", "4");
        //使用map方法,把字元串類型的整數,轉換(映射)為Integer類型的整數
        Stream<Integer> stream2 = stream.map((String s)->{
            return Integer.parseInt(s);
        });
        //周遊Stream2流
        stream2.forEach(i-> System.out.println(i));
    }
}
           

Stream流中的常用方法_count:

用于統計Stream流中元素的個數

long count();
count方法是一個終結方法,傳回值是一個long類型的整數
是以不能再繼續調用Stream流中的其他方法了
           
import java.util.ArrayList;
import java.util.stream.Stream;

public class Demo05Stream_count {
    public static void main(String[] args) {
        //擷取一個Stream流
        ArrayList<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
        list.add(6);
        list.add(7);
        Stream<Integer> stream = list.stream();
        long count = stream.count();
        System.out.println(count);//7
    }
}
           

Stream流中的常用方法_limit:

用于截取流中的元素

limit方法可以對流進行截取,隻取用前n個。方法簽名:
Stream<T> limit(long maxSize);
    參數是一個long型,如果集合目前長度大于參數則進行截取;否則不進行操作
limit方法是一個延遲方法,隻是對流中的元素進行截取,傳回的是一個新的流,是以可以繼續調用Stream流中的其他方法
           
import java.util.stream.Stream;

public class Demo06Stream_limit {
    public static void main(String[] args) {
        //擷取一個Stream流
        String[] arr = {"美羊羊","喜洋洋","懶洋洋","灰太狼","紅太狼"};
        Stream<String> stream = Stream.of(arr);
        //使用limit對Stream流中的元素進行截取,隻要前3個元素
        Stream<String> stream2 = stream.limit(3);
        //周遊stream2流
        stream2.forEach(name-> System.out.println(name));
    }
}
           

Stream流中的常用方法_skip:

用于跳過元素

如果希望跳過前幾個元素,可以使用skip方法擷取一個截取之後的新流:
Stream<T> skip(long n);
    如果流的目前長度大于n,則跳過前n個;否則将會得到一個長度為0的空流。
           
import java.util.stream.Stream;

public class Demo07Stream_skip {
    public static void main(String[] args) {
        //擷取一個Stream流
        String[] arr = {"美羊羊","喜洋洋","懶洋洋","灰太狼","紅太狼"};
        Stream<String> stream = Stream.of(arr);
        //使用skip方法跳過前3個元素
        Stream<String> stream2 = stream.skip(3);
        //周遊stream2流
        stream2.forEach(name-> System.out.println(name));
    }
}
           

Stream流中的常用方法_concat:

用于把流組合到一起

如果有兩個流,希望合并成為一個流,那麼可以使用Stream接口的靜态方法concat
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
           
import java.util.stream.Stream;

public class Demo08Stream_concat {
    public static void main(String[] args) {
        //建立一個Stream流
        Stream<String> stream1 = Stream.of("張三豐", "張翠山", "趙敏", "周芷若", "張無忌");
        //擷取一個Stream流
        String[] arr = {"美羊羊","喜洋洋","懶洋洋","灰太狼","紅太狼"};
        Stream<String> stream2 = Stream.of(arr);
        //把以上兩個流組合為一個流
        Stream<String> concat = Stream.concat(stream1, stream2);
        //周遊concat流
        concat.forEach(name-> System.out.println(name));
    }
}
           

練習:集合元素處理(傳統方式)

現在有兩個ArrayList集合存儲隊伍當中的多個成員姓名,要求使用傳統的for循環(或增強for循環)依次進行以下若幹操作步驟:
    1. 第一個隊伍隻要名字為3個字的成員姓名;存儲到一個新集合中。
    2. 第一個隊伍篩選之後隻要前3個人;存儲到一個新集合中。
    3. 第二個隊伍隻要姓張的成員姓名;存儲到一個新集合中。
    4. 第二個隊伍篩選之後不要前2個人;存儲到一個新集合中。
    5. 将兩個隊伍合并為一個隊伍;存儲到一個新集合中。
    6. 根據姓名建立Person對象;存儲到一個新集合中。
    7. 列印整個隊伍的Person對象資訊。
           
import java.util.ArrayList;

public class Demo01StreamTest {
    public static void main(String[] args) {
        //第一支隊伍
        ArrayList<String> one = new ArrayList<>();
        one.add("迪麗熱巴");
        one.add("宋遠橋");
        one.add("蘇星河");
        one.add("石破天");
        one.add("石中玉");
        one.add("老子");
        one.add("莊子");
        one.add("洪七公");
        //1. 第一個隊伍隻要名字為3個字的成員姓名;存儲到一個新集合中。
        ArrayList<String> one1 = new ArrayList<>();
        for (String name : one) {
            if(name.length()==3){
                one1.add(name);
            }
        }
        //2. 第一個隊伍篩選之後隻要前3個人;存儲到一個新集合中。
        ArrayList<String> one2 = new ArrayList<>();
        for (int i = 0; i <3 ; i++) {
            one2.add(one1.get(i));//i = 0,1,2
        }

        //第二支隊伍
        ArrayList<String> two = new ArrayList<>();
        two.add("古力娜紮");
        two.add("張無忌");
        two.add("趙麗穎");
        two.add("張三豐");
        two.add("尼古拉斯趙四");
        two.add("張天愛");
        two.add("張二狗");
        //3. 第二個隊伍隻要姓張的成員姓名;存儲到一個新集合中。
        ArrayList<String> two1 = new ArrayList<>();
        for (String name : two) {
            if(name.startsWith("張")){
                two1.add(name);
            }
        }
        //4. 第二個隊伍篩選之後不要前2個人;存儲到一個新集合中。
        ArrayList<String> two2 = new ArrayList<>();
        for (int i = 2; i <two1.size() ; i++) {
            two2.add(two1.get(i)); //i 不包含0 1
        }

        //5. 将兩個隊伍合并為一個隊伍;存儲到一個新集合中。
        ArrayList<String> all = new ArrayList<>();
        all.addAll(one2);
        all.addAll(two2);

        //6. 根據姓名建立Person對象;存儲到一個新集合中。
        ArrayList<Person> list = new ArrayList<>();
        for (String name : all) {
            list.add(new Person(name));
        }

        //7. 列印整個隊伍的Person對象資訊。
        for (Person person : list) {
            System.out.println(person);
        }
    }
}

           

練習:集合元素處理(Stream方式)

将上一題當中的傳統for循環寫法更換為Stream流式處理方式。
兩個集合的初始内容不變,Person類的定義也不變。
           
import java.util.ArrayList;
import java.util.stream.Stream;

public class Demo02StreamTest {
    public static void main(String[] args) {
        //第一支隊伍
        ArrayList<String> one = new ArrayList<>();
        one.add("迪麗熱巴");
        one.add("宋遠橋");
        one.add("蘇星河");
        one.add("石破天");
        one.add("石中玉");
        one.add("老子");
        one.add("莊子");
        one.add("洪七公");
        //1. 第一個隊伍隻要名字為3個字的成員姓名;存儲到一個新集合中。
        //2. 第一個隊伍篩選之後隻要前3個人;存儲到一個新集合中。
        Stream<String> oneStream = one.stream().filter(name -> name.length() == 3).limit(3);

        //第二支隊伍
        ArrayList<String> two = new ArrayList<>();
        two.add("古力娜紮");
        two.add("張無忌");
        two.add("趙麗穎");
        two.add("張三豐");
        two.add("尼古拉斯趙四");
        two.add("張天愛");
        two.add("張二狗");
        //3. 第二個隊伍隻要姓張的成員姓名;存儲到一個新集合中。
        //4. 第二個隊伍篩選之後不要前2個人;存儲到一個新集合中。
        Stream<String> twoStream = two.stream().filter(name -> name.startsWith("張")).skip(2);

        //5. 将兩個隊伍合并為一個隊伍;存儲到一個新集合中。
        //6. 根據姓名建立Person對象;存儲到一個新集合中。
        //7. 列印整個隊伍的Person對象資訊。
        Stream.concat(oneStream,twoStream).map(name->new Person(name)).forEach(p-> System.out.println(p));
    }
}
           

第二章 方法引用

在使用Lambda表達式的時候,我們實際上傳遞進去的代碼就是一種解決方案:拿什麼參數做什麼操作。那麼考慮 一種情況:如果我們在Lambda中所指定的操作方案,已經有地方存在相同方案,那是否還有必要再寫重複邏輯?

public class Demo01Printable {
    //定義一個方法,參數傳遞Printable接口,對字元串進行列印
    public static void printString(Printable p) {
        p.print("HelloWorld");
    }

    public static void main(String[] args) {
        //調用printString方法,方法的參數Printable是一個函數式接口,是以可以傳遞Lambda
        printString((s) -> {
            System.out.println(s);
        });

        /*
            分析:
                Lambda表達式的目的,列印參數傳遞的字元串
                把參數s,傳遞給了System.out對象,調用out對象中的方法println對字元串進行了輸出
                注意:
                    1.System.out對象是已經存在的
                    2.println方法也是已經存在的
                是以我們可以使用方法引用來優化Lambda表達式
                可以使用System.out方法直接引用(調用)println方法
         */
        printString(System.out::println);
    }
}

/*
    定義一個列印的函數式接口
 */
@FunctionalInterface
public interface Printable {
    //定義字元串的抽象方法
    void print(String s);
}
           

2.4 方法引用符

雙冒号

::

為引用運算符,而它所在的表達式被稱為方法引用。如果Lambda要表達的函數方案已經存在于某個方 法的實作中,那麼則可以通過雙冒号來引用該方法作為Lambda的替代者。

語義分析

例如上例中, System.out 對象中有一個重載的 println(String) 方法恰好就是我們所需要的。那麼對于 printString 方法的函數式接口參數,對比下面兩種寫法,完全等效:

  • Lambda表達式寫法: s -> System.out.println(s);
  • 方法引用寫法: System.out::println

第一種語義是指:拿到參數之後經Lambda之手,繼而傳遞給 System.out.println 方法去處理。

第二種等效寫法的語義是指:直接讓 System.out 中的 println 方法來取代Lambda。兩種寫法的執行效果完全一 樣,而第二種方法引用的寫法複用了已有方案,更加簡潔。

注:Lambda 中 傳遞的參數 一定是方法引用中 的那個方法可以接收的類型,否則會抛出異常

推導與省略

如果使用Lambda,那麼根據“可推導就是可省略”的原則,無需指定參數類型,也無需指定的重載形式——它們都 将被自動推導。而如果使用方法引用,也是同樣可以根據上下文進行推導。

函數式接口是Lambda的基礎,而方法引用是Lambda的孿生兄弟。

通過對象名引用成員方法

通過對象名引用成員方法
使用前提是對象名是已經存在的,成員方法也是已經存在
就可以使用對象名來引用成員方法
           
/*
    定義一個列印的函數式接口
 */
@FunctionalInterface
public interface Printable {
    //定義字元串的抽象方法
    void print(String s);
}

public class MethodRerObject {
    //定義一個成員方法,傳遞字元串,把字元串按照大寫輸出
    public void printUpperCaseString(String str){
        System.out.println(str.toUpperCase());
    }
}

public class Demo01ObjectMethodReference {
    //定義一個方法,方法的參數傳遞Printable接口
    public static void printString(Printable p){
        p.print("Hello");
    }

    public static void main(String[] args) {
        //調用printString方法,方法的參數Printable是一個函數式接口,是以可以傳遞Lambda表達式
        printString((s)->{
            //建立MethodRerObject對象
            MethodRerObject obj = new MethodRerObject();
            //調用MethodRerObject對象中的成員方法printUpperCaseString,把字元串按照大寫輸出
            obj.printUpperCaseString(s);
        });

        /*
            使用方法引用優化Lambda
            對象是已經存在的MethodRerObject
            成員方法也是已經存在的printUpperCaseString
            是以我們可以使用對象名引用成員方法
         */
        //建立MethodRerObject對象
        MethodRerObject obj = new MethodRerObject();
        printString(obj::printUpperCaseString);
    }
}

           

通過類名引用靜态成員方法

類已經存在,靜态成員方法也已經存在
就可以通過類名直接引用靜态成員方法
           
public class Demo01StaticMethodReference {
    //定義一個方法,方法的參數傳遞要計算絕對值的整數,和函數式接口Calcable
    public static int method(int number,Calcable c){
       return c.calsAbs(number);
    }

    public static void main(String[] args) {
        //調用method方法,傳遞計算絕對值得整數,和Lambda表達式
        int number = method(-10,(n)->{
            //對參數進行絕對值得計算并傳回結果
            return Math.abs(n);
        });
        System.out.println(number);

        /*
            使用方法引用優化Lambda表達式
            Math類是存在的
            abs計算絕對值的靜态方法也是已經存在的
            是以我們可以直接通過類名引用靜态方法
         */
        int number2 = method(-10,Math::abs);
        System.out.println(number2);
    }
}

@FunctionalInterface
public interface Calcable {
    //定義一個抽象方法,傳遞一個整數,對整數進行絕對值計算并傳回
    int calsAbs(int number);
}

           

2.7 通過super引用成員方法

如果存在繼承關系,當Lambda中需要出現super調用時,也可以使用方法引用進行替代。

/*
    定義子類
 */
public class Man extends Human{
    //子類重寫父類sayHello的方法
    @Override
    public void sayHello() {
        System.out.println("Hello 我是Man!");
    }

    //定義一個方法參數傳遞Greetable接口
    public void method(Greetable g){
        g.greet();
    }

    public void show(){
        //調用method方法,方法的參數Greetable是一個函數式接口,是以可以傳遞Lambda
        /*method(()->{
            //建立父類Human對象
            Human h = new Human();
            //調用父類的sayHello方法
            h.sayHello();
        });*/

        //因為有子父類關系,是以存在的一個關鍵字super,代表父類,是以我們可以直接使用super調用父類的成員方法
       /* method(()->{
            super.sayHello();
        });*/

      /*
           使用super引用類的成員方法
           super是已經存在的
           父類的成員方法sayHello也是已經存在的
           是以我們可以直接使用super引用父類的成員方法
       */
      method(super::sayHello);
    }

    public static void main(String[] args) {
        new Man().show();
    }
}

/*
    定義父類
 */
public class Human {
    //定義一個sayHello的方法
    public void sayHello(){
        System.out.println("Hello 我是Human!");
    }
}

/*
    定義見面的函數式接口
 */
@FunctionalInterface
public interface Greetable {
    //定義一個見面的方法
    void greet();
}

           

2.8 通過this引用成員方法

this代表目前對象,如果需要引用的方法就是目前類中的成員方法,那麼可以使用“this::成員方法”的格式來使用方 法引用

/*
    使用this引用本類的成員方法
 */
public class Husband {
    //定義一個買房子的方法
    public void buyHouse(){
        System.out.println("北京二環内買一套四合院!");
    }

    //定義一個結婚的方法,參數傳遞Richable接口
    public void marry(Richable r){
        r.buy();
    }

    //定義一個非常高興的方法
    public void soHappy(){
        //調用結婚的方法,方法的參數Richable是一個函數式接口,傳遞Lambda表達式
       /* marry(()->{
            //使用this.成員方法,調用本類買房子的方法
            this.buyHouse();
        });*/

        /*
            使用方法引用優化Lambda表達式
            this是已經存在的
            本類的成員方法buyHouse也是已經存在的
            是以我們可以直接使用this引用本類的成員方法buyHouse
         */
        marry(this::buyHouse);
    }

    public static void main(String[] args) {
        new Husband().soHappy();
    }
}

/*
    定義一個富有的函數式接口
 */
@FunctionalInterface
public interface Richable {
    //定義一個想買什麼就買什麼的方法
    void buy();
}

           

2.9 類的構造器引用

由于構造器的名稱與類名完全一樣,并不固定。是以構造器引用使用 類名稱::new 的格式表示。

package com.itheima.demo09.ConstructorMethodReference;
/*
    類的構造器(構造方法)引用
 */
public class Demo {
    //定義一個方法,參數傳遞姓名和PersonBuilder接口,方法中通過姓名建立Person對象
    public static void printName(String name,PersonBuilder pb){
        Person person = pb.builderPerson(name);
        System.out.println(person.getName());
    }

    public static void main(String[] args) {
        //調用printName方法,方法的參數PersonBuilder接口是一個函數式接口,可以傳遞Lambda
        printName("迪麗熱巴",(String name)->{
            return new Person(name);
        });

        /*
            使用方法引用優化Lambda表達式
            構造方法new Person(String name) 已知
            建立對象已知 new
            就可以使用Person引用new建立對象
         */
        printName("古力娜紮",Person::new);//使用Person類的帶參構造方法,通過傳遞的姓名建立對象
    }
}

package com.itheima.demo09.ConstructorMethodReference;

public class Person {
    private String name;

    public Person() {
    }

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

package com.itheima.demo09.ConstructorMethodReference;
/*
    定義一個建立Person對象的函數式接口
 */
@FunctionalInterface
public interface PersonBuilder {
    //定義一個方法,根據傳遞的姓名,建立Person對象傳回
    Person builderPerson(String name);
}

           

2.10 數組的構造器引用

數組也是 Object 的子類對象,是以同樣具有構造器,隻是文法稍有不同。如果對應到Lambda的使用場景中時, 需要一個函數式接口

import java.util.Arrays;

/*
    數組的構造器引用
 */
public class Demo {
    /*
        定義一個方法
        方法的參數傳遞建立數組的長度和ArrayBuilder接口
        方法内部根據傳遞的長度使用ArrayBuilder中的方法建立數組并傳回
     */
    public static int[] createArray(int length, ArrayBuilder ab){
        return  ab.builderArray(length);
    }

    public static void main(String[] args) {
        //調用createArray方法,傳遞數組的長度和Lambda表達式
        int[] arr1 = createArray(10,(len)->{
            //根據數組的長度,建立數組并傳回
            return new int[len];
        });
        System.out.println(arr1.length);//10

        /*
            使用方法引用優化Lambda表達式
            已知建立的就是int[]數組
            數組的長度也是已知的
            就可以使用方法引用
            int[]引用new,根據參數傳遞的長度來建立數組
         */
        int[] arr2 =createArray(10,int[]::new);
        System.out.println(Arrays.toString(arr2));
        System.out.println(arr2.length);//10
    }
}

/*
    定義一個建立數組的函數式接口
 */
@FunctionalInterface
public interface ArrayBuilder {
    //定義一個建立int類型數組的方法,參數傳遞數組的長度,傳回建立好的int類型數組
    int[] builderArray(int length);
}