Java8新特性——StreamAPI(一)

1. 流的基本概念

1.1 什麼是流?

流是Java8引入的全新概念,它用來處理集合中的數據,暫且可以把它理解爲一種高級集合。

衆所周知,集合操作非常麻煩,若要對集合進行篩選、投影,需要寫大量的代碼,而流是以聲明的形式操作集合,它就像SQL語句,我們只需告訴流需要對集合進行什麼操作,它就會自動進行操作,並將執行結果交給你,無需我們自己手寫代碼。

因此,流的集合操作對我們來說是透明的,我們只需向流下達命令,它就會自動把我們想要的結果給我們。由於操作過程完全由Java處理,因此它可以根據當前硬件環境選擇最優的方法處理,我們也無需編寫複雜又容易出錯的多線程代碼了。

1.2 流的特點

  1. 只能遍歷一次
    我們可以把流想象成一條流水線,流水線的源頭是我們的數據源(一個集合),數據源中的元素依次被輸送到流水線上,我們可以在流水線上對元素進行各種操作。一旦元素走到了流水線的另一頭,那麼這些元素就被“消費掉了”,我們無法再對這個流進行操作。當然,我們可以從數據源那裏再獲得一個新的流重新遍歷一遍。

  2. 採用內部迭代方式
    若要對集合進行處理,則需我們手寫處理代碼,這就叫做外部迭代。而要對流進行處理,我們只需告訴流我們需要什麼結果,處理過程由流自行完成,這就稱爲內部迭代。

1.3 流的操作種類

流的操作分爲兩種,分別爲中間操作 和 終端操作。

  1. 中間操作
    當數據源中的數據上了流水線後,這個過程對數據進行的所有操作都稱爲“中間操作”。
    中間操作仍然會返回一個流對象,因此多箇中間操作可以串連起來形成一個流水線。

  2. 終端操作
    當所有的中間操作完成後,若要將數據從流水線上拿下來,則需要執行終端操作。
    終端操作將返回一個執行結果,這就是你想要的數據。

1.4 流的操作過程

使用流一共需要三步:

  1. 準備一個數據源
  2. 執行中間操作
    中間操作可以有多個,它們可以串連起來形成流水線。
  3. 執行終端操作
    執行終端操作後本次流結束,你將獲得一個執行結果。

2. 流的使用

2.1 獲取流

在使用流之前,首先需要擁有一個數據源,並通過StreamAPI提供的一些方法獲取該數據源的流對象。數據源可以有多種形式:

  1. 集合
    這種數據源較爲常用,通過stream()方法即可獲取流對象:
List<Person> list = new ArrayList<Person>(); 
Stream<Person> stream = list.stream();
  1. 數組
    通過Arrays類提供的靜態函數stream()獲取數組的流對象:
String[] names = {"chaimm","peter","john"};
Stream<String> stream = Arrays.stream(names);

  1. 直接將幾個值變成流對象:
Stream<String> stream = Stream.of("chaimm","peter","john");
  • 1
  1. 文件
    try(Stream lines = Files.lines(Paths.get(“文件路徑名”),Charset.defaultCharset())){
    //可對lines做一些操作
    }catch(IOException e){
    }
    PS:Java7簡化了IO操作,把打開IO操作放在try後的括號中即可省略關閉IO的代碼。

2.2 篩選filter

filter函數接收一個Lambda表達式作爲參數,該表達式返回boolean,在執行過程中,流將元素逐一輸送給filter,並篩選出執行結果爲true的元素。
如,篩選出所有學生:

List<Person> result = list.stream()
                    .filter(Person::isStudent)
                    .collect(toList());
  •  

2.3 去重distinct

去掉重複的結果:

List<Person> result = list.stream()
                    .distinct()
                    .collect(toList());

2.4 截取

截取流的前N個元素:

List<Person> result = list.stream()
                    .limit(3)
                    .collect(toList());

2.5 跳過

跳過流的前n個元素:

List<Person> result = list.stream()
                    .skip(3)
                    .collect(toList());

2.6 映射

對流中的每個元素執行一個函數,使得元素轉換成另一種類型輸出。流會將每一個元素輸送給map函數,並執行map中的Lambda表達式,最後將執行結果存入一個新的流中。
如,獲取每個人的姓名(實則是將Perosn類型轉換成String類型):

List<Person> result = list.stream()
                    .map(Person::getName)
                    .collect(toList());

2.7 合併多個流

例:列出List中各不相同的單詞,List集合如下:

List<String> list = new ArrayList<String>();
list.add("I am a boy");
list.add("I love the girl");
list.add("But the girl loves another girl");

思路如下:

  • 首先將list變成流:
list.stream();
  • 1
  • 按空格分詞:
list.stream()
            .map(line->line.split(" "));
  • 1
  • 2

分完詞之後,每個元素變成了一個String[]數組。

  • 將每個String[]變成流:
list.stream()
            .map(line->line.split(" "))
            .map(Arrays::stream)
  • 1
  • 2
  • 3

此時一個大流裏面包含了一個個小流,我們需要將這些小流合併成一個流。

  • 將小流合併成一個大流:
    用flagmap替換剛纔的map
list.stream()
            .map(line->line.split(" "))
            .flagmap(Arrays::stream)
  • 去重
list.stream()
            .map(line->line.split(" "))
            .flagmap(Arrays::stream)
            .distinct()
            .collect(toList());

2.8 是否匹配任一元素:anyMatch

anyMatch用於判斷流中是否存在至少一個元素滿足指定的條件,這個判斷條件通過Lambda表達式傳遞給anyMatch,執行結果爲boolean類型。
如,判斷list中是否有學生:

boolean result = list.stream()
            .anyMatch(Person::isStudent);

2.9 是否匹配所有元素:allMatch

allMatch用於判斷流中的所有元素是否都滿足指定條件,這個判斷條件通過Lambda表達式傳遞給anyMatch,執行結果爲boolean類型。
如,判斷是否所有人都是學生:

boolean result = list.stream()
            .allMatch(Person::isStudent);

2.10 是否未匹配所有元素:noneMatch

noneMatch與allMatch恰恰相反,它用於判斷流中的所有元素是否都不滿足指定條件:

boolean result = list.stream()
            .noneMatch(Person::isStudent);

2.11 獲取任一元素findAny

findAny能夠從流中隨便選一個元素出來,它返回一個Optional類型的元素。

Optional<Person> person = list.stream()
                                    .findAny();

Optional介紹

Optional是Java8新加入的一個容器,這個容器只存1個或0個元素,它用於防止出現NullpointException,它提供如下方法:

  • isPresent()
    判斷容器中是否有值。
  • ifPresent(Consume lambda)
    容器若不爲空則執行括號中的Lambda表達式。
  • T get()
    獲取容器中的元素,若容器爲空則拋出NoSuchElement異常。
  • T orElse(T other)
    獲取容器中的元素,若容器爲空則返回括號中的默認值。

2.12 獲取第一個元素findFirst

Optional<Person> person = list.stream()
                                    .findFirst();

2.13 歸約

歸約是將集合中的所有元素經過指定運算,摺疊成一個元素輸出,如:求最值、平均數等,這些操作都是將一個集合的元素摺疊成一個元素輸出。

在流中,reduce函數能實現歸約。
reduce函數接收兩個參數:

  • 初始值
  • 進行歸約操作的Lambda表達式

2.13.1 元素求和:自定義Lambda表達式實現求和

例:計算所有人的年齡總和

int age = list.stream().reduce(0, (person1,person2)->person1.getAge()+person2.getAge());
  •  

reduce的第一個參數表示初試值爲0;
reduce的第二個參數爲需要進行的歸約操作,它接收一個擁有兩個參數的Lambda表達式,reduce會把流中的元素兩兩輸給Lambda表達式,最後將計算出累加之和。

2.13.2 元素求和:使用Integer.sum函數求和

上面的方法中我們自己定義了Lambda表達式實現求和運算,如果當前流的元素爲數值類型,那麼可以使用Integer提供了sum函數代替自定義的Lambda表達式,如:

int age = list.stream().reduce(0, Integer::sum);
  •  

Integer類還提供了min、max等一系列數值操作,當流中元素爲數值類型時可以直接使用。

2.14 數值流的使用

採用reduce進行數值操作會涉及到基本數值類型和引用數值類型之間的裝箱、拆箱操作,因此效率較低。
當流操作爲純數值操作時,使用數值流能獲得較高的效率。

2.14.1 將普通流轉換成數值流

StreamAPI提供了三種數值流:IntStream、DoubleStream、LongStream,也提供了將普通流轉換成數值流的三種方法:mapToInt、mapToDouble、mapToLong。
如,將Person中的age轉換成數值流:

IntStream stream = list.stream()
                            .mapToInt(Person::getAge);

2.14.2 數值計算

每種數值流都提供了數值計算函數,如max、min、sum等。
如,找出最大的年齡:

OptionalInt maxAge = list.stream()
                                .mapToInt(Person::getAge)
                                .max();

由於數值流可能爲空,並且給空的數值流計算最大值是沒有意義的,因此max函數返回OptionalInt,它是Optional的一個子類,能夠判斷流是否爲空,並對流爲空的情況作相應的處理。
此外,mapToInt、mapToDouble、mapToLong進行數值操作後的返回結果分別爲:OptionalInt、OptionalDouble、OptionalLong

T

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