Java8中你可能不知道的一些地方之Stream實戰

悠腫椒貧 發表於 2020-09-12

  說起流,我們會想起手機 ,電腦組裝流水線,物流倉庫商品包裝流水線等等。如果把手機 ,電腦,包裹看做最終結果的話,那麼加工商品前的各種零部件就可以看做資料來源,而中間一系列的加工作業操作,就可以看做流的處理。

  流的概念

  Java Se中對於流的操作有輸入輸出IO流,而Java8中引入的Stream 屬於Java API中的一個新成員,它允許你以宣告性方式處理資料集合,Stream 使用一種類似 SQL 語句從資料庫查詢資料的直觀方式來提供一種對 Java 集合運算和表達的高階抽象。 注意這裡的流操作可以看做是對集合資料的處理。

  簡單來說,流是一種資料渠道,用於運算元據源(集合、陣列、檔案等)所生產的元素序列。

  源-流會使用一個提供資料的源,如集合、陣列或輸入|輸出資源。

  從有序集生成流時會保留原有的順序。由列表生成的流,其元素順序與列表一致

  元素序列-就像集合一樣,流也提供了一個介面,可以訪問特定元素型別的一組有序值。

  資料處理操作-流的資料處理功能支援類似於資料庫的操作(資料篩選、過濾、排序等操作)。

  流水線-多個流操作本身會返回一個流,多個操作就可以連結起來,成為資料處理的一道流水線。

  流&集合

  計算的時期

  Java8中你可能不知道的一些地方之Stream實戰

  集合中資料都是計算完畢的資料,例如從資料庫中查詢使用者記錄 按使用者id 查詢 降序排列 然後通過list 接收使用者記錄,資料的計算已在放入集合前完成。

  流中資料按需計算,按照使用者的需要計算資料,例如通過搜尋引擎進行搜尋,搜尋出來的條目並不是全部呈現出來的,而且先顯示最符合的前 10 條或者前 20 條,只有在點選 “下一頁” 的時候,才會再輸出新的 10 條。流的計算也是這樣,當使用者需要對應資料時,Stream 才會對其進行計算處理。

  外部迭代與內部迭代

  Java8中你可能不知道的一些地方之Stream實戰

  把集合比作一個工廠的倉庫的話,一開始工廠硬體比較落後,要對貨物作什麼修改,此時工人親自走進倉庫對貨物進行處理,有時候還要將處理後的貨物轉運到另一個倉庫中。此時對於開發者來說需要親自去做迭代,一個個地找到需要的貨物,並進行處理,這叫做外部迭代。

  當工廠發展起來後,配備了流水線作業,工廠只要根據需求設計出相應的流水線,然後工人只要把貨物放到流水線上,就可以等著接收成果了,而且流水線還可以根據要求直接把貨物輸送到相應的倉庫。

  這就叫做內部迭代,流水線已經幫你把迭代給完成了,你只需要說要幹什麼就可以了(即設計出合理的流水線)。相當於 Java8 引入的Stream 對資料的處理實現了”自動化”操作。

  流操作過程

  Java8中你可能不知道的一些地方之Stream實戰

  整個流操作就是一條流水線,將元素放在流水線上一個個地進行處理。需要注意的是:很多流操作本身就會返回一個流,所以多個操作可以直接連線起來, 如下圖這樣,操作可以進行鏈式呼叫,並且並行流還可以實現資料流並行處理操作。

  Java8中你可能不知道的一些地方之Stream實戰

  1 集合建立流

  在 Java 8 中, 集合介面有兩個方法來生成流:

  stream() − 為集合建立序列流。

  parallelStream() − 為集合建立並行流。

  示例程式碼如下:

  public static void main(String[] args) {

  /**

  * 定義集合l1 併為集合建立序列流

  */

  List<String> l1 = Arrays.asList("周星馳", "周杰倫", "周星星", "周潤發");

  // 返回序列流

  l1.stream();

  // 返回並行流

  l1.parallelStream();

  }

  上述操作得到的流是通過原始資料轉換過來的流,除了這種流建立的基本操作外,對於流的建立還有以下幾種方式。

  2 值建立流

  Stream.of(T...) : Stream.of("aa", "bb") 生成流

  //值建立流 生成一個字串流

  Stream<String> stream = Stream.of("java8", "Spring", "SpringCloud");

  stream.forEach(System.out::println);

  3 陣列建立流

  根據引數的陣列型別建立對應的流。

  Arrays.stream(T[ ])

  Arrays.stream(int[ ])

  Arrays.stream(double[ ])

  Arrays.stream(long[ ])

  /**

  * 這裡以int 為例   long double 不再舉例

  */

  Stream stream = Arrays.stream(Arrays.asList(10, 20, 30, 40).toArray());

  // 根據陣列索引範圍建立指定Stream

  stream = Arrays.stream(Arrays.asList(10, 20, 30, 40).toArray(), 0, 2);

  4 檔案生成流

  stream = Files.lines(Paths.get("C:\\java\\jdbc.properties"));

  System.out.println(stream.collect(Collectors.toList()));

  // 指定字符集編碼

  stream = Files.lines(Paths.get("C:\\java\\jdbc.properties"), Charset.forName("utf-8"));

  System.out.println(stream.collect(Collectors.toList()));

  5 函式生成流

  兩個方法:

  iterate : 依次對每個新生成的值應用函式

  generate :接受一個函式,生成一個新的值

  // 重100 開始 生成偶數流

  Stream.iterate(100, n -> n + 2);

  // 產生1-100 隨機數

  Stream.generate(() ->(int) (Math.random() * 100 + 1));

  流中間操作

  流的中間操作分為三大類:篩選切片、對映、排序。

  篩選切片:類似sql 中where 條件判斷的意思,對元素進行篩選操作

  對映:對元素結果進行轉換 ,優點類似select 欄位意思或者對元素內容進行轉換處理

  排序:比較好理解 ,常用sql 中按欄位升序 降序操作

  Java8中你可能不知道的一些地方之Stream實戰

  流中間運算元據準備(這裡以訂單資料處理為例)

  @Data

  public class Order {

  // 訂單id

  private Integer id;

  // 訂單使用者id

  private Integer userId;

  // 訂單編號

  private String orderNo;

  // 訂單日期

  private Date orderDate;

  // 收貨地址

  private String address;

  // 建立時間

  private Date createDate;

  // 更新時間

  private Date updateDate;

  // 訂單狀態  0-未支付  1-已支付  2-待發貨  3-已發貨  4-已接收  5-已完成

  private Integer status;

  // 是否有效  1-有效訂單  0-無效訂單

  private Integer isValid;

  //訂單總金額

  private  Double total;

  }

  Order order01 = new Order(1, 10, "20190301",

  new Date(), "上海市-浦東區", new Date(), new Date(), 4, 1, 100.0);

  Order order02 = new Order(2, 30, "20190302",

  new Date(), "北京市四惠區", new Date(), new Date(), 1, 1, 2000.0);

  Order order03 = new Order(3, 20, "20190303",

  new Date(), "北京市-朝陽區", new Date(), new Date(), 4, 1, 500.0);

  Order order04 = new Order(4, 40, "20190304",

  new Date(), "北京市-大興區", new Date(), new Date(), 4, 1, 256.0);

  Order order05 = new Order(5, 40, "20190304",

  new Date(), "上海市-松江區", new Date(), new Date(), 4, 1, 1000.0);

  ordersList = Arrays.asList(order01, order02, order03, order04, order05);

  篩選&切片

  篩選有效訂單

  // 過濾有效訂單

  ordersList.stream().filter((order) -> order.getIsValid() == 1)

  .forEach(System.out::println);

  篩選有效訂單 取第一頁資料(每頁2條記錄)

  // 過濾有效訂單 取第一頁資料(每頁2條記錄)

  ordersList.stream().filter((order) -> order.getIsValid() == 1)

  .limit(2)

  .forEach(System.out::println);

  篩選訂單集合有效訂單 取最後一條記錄

  // 過濾訂單集合有效訂單 取最後一條記錄

  ordersList.stream().filter((order) -> order.getIsValid() == 1)

  .skip(ordersList.size() - 2)  // 跳過前ordersList.size()-2 記錄

  .forEach(System.out::println);

  篩選有效訂單 取第3頁資料(每頁2條記錄)

  // 過濾有效訂單 取第3頁資料(每頁2條記錄) 並列印到控制檯

  ordersList.stream().filter((order) -> order.getIsValid() == 1)

  .skip((3 - 1) * 2)

  .limit(2)

  .forEach(System.out::println);

  篩選無效訂單去除重複訂單號記錄

  // 過濾無效訂單 去除重複訂單號記錄  重寫Order equals 與 hashCode 方法

  ordersList.stream().filter((order) -> order.getIsValid() == 0)

  .distinct()

  .forEach(System.out::println);

  對映

  過濾有效訂單,獲取所有訂單編號

  //過濾有效訂單,獲取所有訂單編號

  ordersList.stream().filter((order) -> order.getIsValid() == 1)

  .map((order) -> order.getOrderNo())

  .forEach(System.out::println);

  過濾有效訂單 ,並分離每個訂單下收貨地址市區資訊

  ordersList.stream().map(o -> o.getAddress()

  .split("-"))

  .flatMap(Arrays::stream)

  .forEach(System.out::println);

  排序

  過濾有效訂單 根據使用者id 進行排序

  //過濾有效訂單 根據使用者id 進行排序

  ordersList.stream().filter((order) -> order.getIsValid() == 1)

  .sorted((o1, o2) -> o1.getUserId() - o2.getUserId())

  .forEach(System.out::println);

  //或者等價寫法

  ordersList.stream().filter((order) -> order.getIsValid() == 1)

  .sorted(Comparator.comparingInt(Order::getUserId))

  .forEach(System.out::println);

  過濾有效訂單 ,根據訂單狀態排序 如果訂單狀態相同根據訂單建立時間排序

  //過濾有效訂單  如果訂單狀態相同 根據訂單建立時間排序 反之根據訂單狀態排序

  ordersList.stream().filter((order) -> order.getIsValid() == 1)

  .sorted((o1, o2) -> {

  if (o1.getStatus().equals(o2.getStatus())) {

  return o1.getCreateDate().compareTo(o2.getCreateDate());

  } else {

  return o1.getStatus().compareTo(o2.getStatus());

  }})

  .forEach(System.out::println);

  // 等價形式

  ordersList.stream().filter((order) -> order.getIsValid() == 1)

  .sorted(Comparator.comparing(Order::getCreateDate)

  .thenComparing(Comparator.comparing(Order::getStatus)))

  .forEach(System.out::println);

  流的終止操作

  終止操作會從流的流水線生成結果。其結果是任何不是流的值,比如常見的List、 Integer,甚 至void等結果。對於流的終止操作,分為以下三類:

  Java8中你可能不知道的一些地方之Stream實戰

  查詢與匹配

  篩選有效訂單 匹配是否全部為已支付訂單

  // 篩選有效訂單 匹配是否全部為已支付訂單

  System.out.println("allMatch匹配結果:" +

  ordersList.stream()

  .filter((order) -> order.getIsValid() == 1)

  .allMatch((o) -> o.getStatus() != 0)

  );

  篩選有效訂單 匹配是否存在未支付訂單

  // 篩選有效訂單 匹配是否存在未支付訂單

  System.out.println("anyMatch匹配結果:" +

  ordersList.stream()

  .filter((order) -> order.getIsValid() == 1)

  .anyMatch((o) -> o.getStatus() == 0)

  );

  篩選有效訂單 全部未完成訂單

  // 篩選有效訂單 全部未完成訂單

  System.out.println("noneMatch匹配結果:" +

  ordersList.stream()

  .filter((order) -> order.getIsValid() == 1)

  .noneMatch((o) -> o.getStatus() == 5)

  );

  篩選有效訂單 返回第一條訂單

  // 篩選有效訂單 返回第一條訂單

  System.out.println("findAny匹配結果:"+

  ordersList.stream()

  .filter((order) -> order.getIsValid() == 1)

  .findAny()

  .get()

  );

  篩選所有有效訂單 返回訂單總數

  //  篩選所有有效訂單 返回訂單總數

  System.out.println("count結果:" +

  ordersList.stream()

  .filter((order) -> order.getIsValid() == 1)

  .count()

  );

  篩選有效訂單 返回金額最大訂單金額

  // 篩選有效訂單 返回金額最大訂單金額

  System.out.println("訂單金額最大值:" +

  ordersList.stream()

  .filter((order) -> order.getIsValid() == 1)

  .map(Order::getTotal)

  .max(Double::compare)

  .get()

  );

  篩選有效訂單 返回金額最小訂單金額

  // 篩選有效訂單 返回金額最小訂單金額

  System.out.println("訂單金額最小值:" +

  ordersList.stream()

  .filter((order) -> order.getIsValid() == 1)

  .map(Order::getTotal)

  .min(Double::compare)

  .get()

  );

  流的應用

  Java8引入Stream流操作,使得對元素的處理更加的方便快捷,通過Stream提供的相關方法很好的結合Lambda、函式式介面、方法引用等相關內容,使得流的處理相比較原始集合處理程式碼極大簡化,Stream支援函式的鏈式呼叫,程式碼上更加緊湊同時Stream支援的元素的並行化處理提高了程式的執行效能。

  對於Stream流的應用通常在集合元素資料處理上特別是對元素需要進行多次處理的情況,同時對於函數語言程式設計的味道更加濃重,也是以後開發的一個趨勢。

  好了,Java8核心特性之Stream流就介紹到這裡了,應該是非常詳盡了,希望大家喜歡。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69920891/viewspace-2719013/,如需轉載,請註明出處,否則將追究法律責任。