天天看點

java8 stream 分組_Java8中你可能不知道的一些地方之Stream實戰

java8 stream 分組_Java8中你可能不知道的一些地方之Stream實戰
java8 stream 分組_Java8中你可能不知道的一些地方之Stream實戰

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

流的概念

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

簡單來說,流是一種資料管道,用于操作資料源(集合、數組、檔案等)所生産的元素序列。

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

從有序集生成流時會保留原有的順序。由清單生成的流,其元素順序與清單一緻

  • 元素序列-就像集合一樣,流也提供了一個接口,可以通路特定元素類型的一組有序值。
  • 資料處理操作-流的資料處理功能支援類似于資料庫的操作(資料篩選、過濾、排序等操作)。
  • 流水線-多個流操作本身會傳回一個流,多個操作就可以連結起來,成為資料處理的一道流水線。

流&集合

  • 計算的時期
java8 stream 分組_Java8中你可能不知道的一些地方之Stream實戰

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

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

  • 外部疊代與内部疊代
java8 stream 分組_Java8中你可能不知道的一些地方之Stream實戰

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

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

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

流操作過程

java8 stream 分組_Java8中你可能不知道的一些地方之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:javajdbc.properties"));
System.out.println(stream.collect(Collectors.toList()));
// 指定字元集編碼
stream = Files.lines(Paths.get("C:javajdbc.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 分組_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 分組_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()
                          );
           

歸約&收集

1 歸約

将流中元素反複結合起來,得到一個值的操作

  • 計算有效訂單總金額
// 計算有效訂單總金額
        System.out.println("有效訂單總金額:" + 
                            ordersList.stream()
                                .filter((order) -> order.getIsValid() == 1)
                                .map(Order::getTotal)
                                .reduce(Double::sum)
                                .get()
                          );
           

2 Collector資料收集

将流轉換為其他形式,coollect 方法作為終端操作, 接收一個Collector接口的實作,用于給Stream中元素做彙總的方法。最常用的方法,把流中所有元素收集到一個 List, Set 或 Collection中。

3 集合收集

常用集合收集方法 toList、toSet、toCollection、toMap等

  • 篩選所有有效訂單 并收集訂單清單
// 篩選所有有效訂單并收集訂單清單
ordersList.stream()
    .filter((order) -> order.getIsValid() == 1)
    .collect(Collectors.toList())
    .forEach(System.out::println);
           
  • 篩選所有有效訂單并收集訂單号與訂單金額
// 篩選所有有效訂單 并收集訂單号 與 訂單金額
Map<String,Double> map=ordersList.stream().filter((order) -> order.getIsValid() == 1).
    collect(Collectors.toMap(Order::getOrderNo, Order::getTotal));
// java8 下對map進行周遊操作 如果 Map的Key重複,會報錯
map.forEach((k,v)->{
    System.out.println("k:"+k+":v:"+v);
});
           

彙總

彙總操作在Stream流操作比較常見,比如計算總數,求平均等操作,常用方法如下:

java8 stream 分組_Java8中你可能不知道的一些地方之Stream實戰

相關操作如下

  • 篩選所有有效訂單 傳回訂單總數
System.out.println("count結果:"+
                        ordersList.stream()
                            .filter((order) -> order.getIsValid() == 1)
                            .collect(Collectors.counting())
                  );
System.out.println("count結果:"+
                        ordersList.stream()
                            .filter((order) -> order.getIsValid() == 1)
                            .count()
                  );
           
  • 傳回訂單總金額
System.out.println("訂單總金額:"+
                        ordersList.stream()
                            .filter((order) -> order.getIsValid() == 1)
                            .collect(Collectors.summarizingDouble(Order::getTotal))
                  );
System.out.println("訂單總金額:"+
                        ordersList.stream()
                            .filter((order) -> order.getIsValid() == 1)
                            .mapToDouble(Order::getTotal)
                            .sum()
                  );
System.out.println("訂單總金額:"+
                        ordersList.stream()
                            .filter((order) -> order.getIsValid() == 1)
                            .map(Order::getTotal)
                            .reduce(Double::sum)
                            .get()
                  );
           
  • 傳回使用者id=20 有效訂單平均每筆消費金額
System.out.println("使用者id=20 有效訂單平均每筆消費金額:"+
                        ordersList.stream()
                            .filter((order) -> order.getIsValid() == 1)
                            .filter((order -> order.getUserId()==20))
                            .collect(Collectors.averagingDouble(Order::getTotal))
                  );
System.out.println("使用者id=20 有效訂單平均每筆消費金額:"+
                        ordersList.stream()
                            .filter((order) -> order.getIsValid() == 1)
                            .filter((order -> order.getUserId()==20))
                            .mapToDouble(Order::getTotal)
                            .average()
                            .getAsDouble()
                  );

System.out.println("使用者id=20 有效訂單平均每筆消費金額:"+
                        ordersList.stream()
                            .filter((order) -> order.getIsValid() == 1)
                            .filter((order -> order.getUserId()==20))
                            .collect(Collectors.summarizingDouble(Order::getTotal))
                            .getAverage()
                  );
           
  • 篩選所有有效訂單 并計算訂單總金額
System.out.println("訂單總金額:"+
                        ordersList.stream()
                            .filter((order) -> order.getIsValid() == 1)
                            .collect(Collectors.summingDouble(Order::getTotal))
                  );
           

最值

  • 篩選所有有效訂單 并計算最小訂單金額
System.out.println("最小訂單金額:"+
                        ordersList.stream()
                            .filter((order) -> order.getIsValid() == 1)
                            .map(Order::getTotal)
                            .collect(Collectors.minBy(Double::compare))
                  );
           
  • 篩選所有有效訂單 并計算最大訂單金額
// 篩選所有有效訂單 并計算最大訂單金額
System.out.println("最大訂單金額:"+
                        ordersList.stream()
                            .filter((order) -> order.getIsValid() == 1)
                            .map(Order::getTotal)
                            .collect(Collectors.maxBy(Double::compare))
                  );
           

分組&分區

1 分組

groupingBy 用于将資料分組,最終傳回一個 Map 類型 ,groupingBy 第二參數用于實作多級分組

  • 根據有效訂單支付狀态進行分組操作
Map<Integer,List<Order>> g01=ordersList.stream()
                                .filter((order) -> order.getIsValid() == 1)
                                .collect(Collectors.groupingBy(Order::getStatus));
g01.forEach((status,order)->{
    System.out.println("----------------");
    System.out.println("訂單狀态:"+status);
    order.forEach(System.out::println);
});
           
  • 篩選有效訂單,根據使用者id 和 支付狀态進行分組
Map<Integer,Map<String,List<Order>>> g02= ordersList.stream()
                    .filter((order) -> order.getIsValid() == 1)
                    .collect(Collectors.groupingBy(Order::getUserId,
                              Collectors.groupingBy((o)->{
                                    if(o.getStatus()==0){
                                        return "未支付";
                                    }else if (o.getStatus()==1){
                                        return "已支付";
                                    }else if (o.getStatus()==2){
                                        return "待發貨";
                                    }else if (o.getStatus()==3){
                                        return "已發貨";
                                    }else if (o.getStatus()==4){
                                        return "已接收";
                                    } else{
                                        return "已完成";
                                    }
                                }
                                ))
                            );
g02.forEach((userId,m)->{
    System.out.println("使用者id:"+userId+"-->有效訂單如下:");
    m.forEach((status,os)->{
        System.out.println("狀态:"+status+"---訂單清單如下:");
        os.forEach(System.out::println);
    });
    System.out.println("-----------------------");
});
           

2 分區

分區與分組的差別在于,分區是按照 true 和 false 來分的,是以partitioningBy 接受的參數的 lambda 也是

T -> boolean

  • 分區操作-篩選訂單金額>1000 的有效訂單
Map<Boolean,List<Order>> g03= ordersList.stream()
            .filter((order) -> order.getIsValid() == 1)
            .collect(Collectors.partitioningBy((o)->o.getTotal()>1000));
g03.forEach((b,os)->{
    System.out.println("分區結果:"+b+"--清單結果:");
    os.forEach(System.out::println);
});
           
  • 拼接操作-篩選有效訂單并進行拼接
String orderStr=ordersList.stream()
            .filter((order) -> order.getIsValid() == 1)
            .map(Order::getOrderNo)
            .collect(Collectors.joining(","));
System.out.println(orderStr);
           

流的應用

Java8引入Stream流操作,使得對元素的處理更加的友善快捷,通過Stream提供的相關方法很好的結合Lambda、函數式接口、方法引用等相關内容,使得流的處理相比較原始集合處理代碼極大簡化,Stream支援函數的鍊式調用,代碼上更加緊湊同時Stream支援的元素的并行化處理提高了程式的執行性能。

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

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