Java 流(Stream)操作實例 - 篩選、映射、查找匹配

準備工作

構建一個測試類,通過測試類先初始化一個數據源,具體如下。

public class TestObject {
 private String name;
 private String sex;
 private int age;
 private String email;
 private boolean isMng;
 
 public TestObject() {
 }
 
 public TestObject(String name,String sex,int age,String email,boolean isMng){
  this.name=name;
  this.sex=sex;
  this.age=age;
  this.email=email;
  this.isMng=isMng;
 }
//... ...get、set方法就不貼出來了,多又麻煩
}

在測試類中定義初始化數據源

public class StreamOperation {
 static List<TestObject> list = Arrays.asList(
   new TestObject("Ron","M",10,"ron.zheng@tfschange.com",false),
   new TestObject("KDS","W",10,"kds@qq.com",false),
   new TestObject("BoDuo","W",30,"boduo@163.com",false),
   new TestObject("CangJin","W",10,"cangjin@gmail.com",false),
   new TestObject("XiaoZe","W",30,"xiaoze@hotmail.com",true),
   new TestObject("James","M",10,"leblonjames@hotmail.com",true),
   new TestObject("Allen","M",50,"allen.lei@tfschange.com",true),
   new TestObject("Smith","M",10,"jr.smith@cel.com",true),
   new TestObject("Wade","M",20,"dw.wade@cel.com",true),
   new TestObject("Wade","M",20,"dw.wade@cel.com",false)
   );
//... ...流操作
}

用謂詞篩選

Streams 接口支持 filter 方法,該操作會接受一個謂詞(一個返回 boolean 的函數)作爲參數,並返回一個包括所有符合謂詞的元素的流。比如我們需要篩選 isMng 爲 ture 的數據並打印名字就可以按照如下的方式處理。

/**
 * @Comment 獲取Leader
 * @Author Ron
 * @return
 */
public static List<TestObject> getLeader() {
 return list.stream().filter(TestObject::isMng).collect(Collectors.toList());
}

public static void main(String[] args) {
 List<TestObject> leaders = getLeader();
 leaders.stream().forEach(leader->System.out.println(leader.getName()));
}

篩選各異的元素

流還支持一個叫作 distinct 的方法,它會返回一個元素各異(根據流所生成元素的 hashCode 和 equals 方法實現)的流。例如,以下代碼會篩選出列表中所有的偶數,並確保沒有重複。

public static void main(String[] args) {
  List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
  numbers.stream().filter(i -> i % 2 == 0).distinct().forEach(System.out::println);
 }

截短流

流支持 limit(n) 方法,該方法會返回一個不超過給定長度的流。所需的長度作爲參數傳遞給 limit。如果流是有序的,則最多會返回前 n 個元素。比如選出前 5 個 sex 爲 M 的對象並打印其名稱可以按如下的代碼操作。

list.stream().filter(u->u.getSex().equals("M")).limit(5).forEach(u->System.out.println(u.getName()));

如果我們需要選出前 5 個 sex 爲 M 的對象並按照名稱排序之後打印其名稱可以按如下的代碼操作。

list.stream()
  .filter(u->u.getSex().equals("M"))
  .limit(5)
  .sorted(Comparator.comparing(TestObject::getName))
  .forEach(u->System.out.println(u.getName()));

跳過元素

流還支持 skip(n) 方法,返回一個扔掉了前 n 個元素的流。如果流中元素不足 n 個,則返回一個空流。請注意, limit(n) 和 skip(n) 是互補的。例如,下面的代碼將會跳過篩選出來的第一個元素並打印名字。

list.stream()
  .filter(u->u.getSex().equals("M"))
  .sorted(Comparator.comparing(TestObject::getName))
  .skip(1)
  .forEach(u->System.out.println(u.getName()));

對流中每一個元素應用函數

流支持 map 方法,它會接受一個函數作爲參數。這個函數會被應用到每個元素上,並將其映射成一個新的元素(使用映射一詞,是因爲它和轉換類似,但其中的細微差別在於它是 “創建一個新版本” 而不是去“修改”)。

例如,下面的代碼把方法引用 TestObject::getName 傳給了 map 方法,來提取流中用戶的名稱並打印:

list.stream()
.map(TestObject::getName)
.collect(Collectors.toList())
.forEach(System.out::println);

因爲 getName 方法返回一個 String,所以 map 方法輸出的流的類型就是 Stream⁢String。(搜索公衆號 Java 知音,回覆 “2021”,送你一份 Java 面試題寶典)

我們來再看一個例子,我們把方法引用 TestObject::getName 傳給了 map 方法,來提取流中用戶的名稱,然後再打印用戶名稱的長度。你可以像下面這樣,給 map 傳遞一個方法引用 String::length 來解決這個問題:

list.stream()
  .map(TestObject::getName)
  .map(String::length)
  .collect(Collectors.toList())
  .forEach(System.out::println);

檢查謂詞是否至少匹配一個元素

anyMatch 方法可以回答 “流中是否有一個元素能匹配給定的謂詞”。比如,你可以用它來看看用戶列表裏面是否有名稱爲 Ron 的對象可選擇:

if(list.stream().anyMatch(u->u.getName().equals("Ron"))){
  System.out.println("Ron已經到了");
 }

anyMatch 方法返回一個 boolean,因此是一個終端操作。

檢查謂詞是否匹配所有元素

allMatch 方法的工作原理和 anyMatch 類似,但它會看看流中的元素是否都能匹配給定的謂詞。比如,你可以用它來看看用戶是否都大於 10 歲。

if(list.stream().allMatch(u->u.getAge()>=10)){
  System.out.println("很棒,都大於10歲");
 }else{
  System.out.println("原來都還沒發育");
 }

和 allMatch 相對的是 noneMatch。它可以確保流中沒有任何元素與給定的謂詞匹配。比如,你可以用 noneMatch 重寫前面的例子:

if(list.stream().noneMatch(u->u.getAge()<10)){
  System.out.println("很棒,都大於10歲");
 }else{
  System.out.println("原來都還沒發育");
 }

anyMatch、 allMatch 和 noneMatch 這三個操作都用到了我們所謂的短路,這就是大家熟悉的 Java 中 && 和 || 運算符短路在流中的版本。

Optional 簡介

Optional<T>類(java.util.Optional)是一個容器類,代表一個值存在或不存在。Java 8 的庫設計人員引入了Optional<T>,這樣就不用返回衆所周知容易出問題的 null 了。Optional 裏面幾種可以迫使你顯式地檢查值是否存在或處理值不存在的情形。

查找元素

findAny 方法將返回當前流中的任意元素。它可以與其他流操作結合使用。

例如,我們需要顯示的檢查是否存在一個名爲‘Ron’的人並顯示其名稱就可以按照如下的代碼操作。

list.stream()
  .filter(u->u.getName().equals("Ron"))
  .findAny()
  .ifPresent(u->System.out.println(u.getName()));

流水線將在後臺進行優化使其只需走一遍,並在利用短路找到結果時立即結束。

查找第一個元素

有些流有一個出現順序(encounter order)來指定流中項目出現的邏輯順序(比如由 List 或排序好的數據列生成的流)。對於這種流,你可能想要找到第一個元素。爲此有一個 findFirst 方法,它的工作方式類似於 findany。

例如我們需要找到第一個 isLeader 爲 ture 的對象並打印其名字,就可以按照如下的代碼操作。

list.stream()
  .filter(u->u.isLeader())
  .findFirst()
  .ifPresent(u->System.out.println(u.getName()));

何時使用 findFirst 和 findAny

你可能會想,爲什麼會同時有 findFirst 和 findAny 呢?答案是並行。找到第一個元素在並行上限制更多。如果你不關心返回的元素是哪個,請使用 findAny,因爲它在使用並行流時限制較少。

參考:Java8 實戰

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/bBLu_SvAJQzoax_hOYppCw