Java8 Streams 讓集合操作飛起來

  • 2019 年 10 月 3 日
  • 筆記

前言

接上篇文章 java8 新特性 由於上篇過於龐大,使得重點不夠清晰,本篇單獨拿出 java8 的 Stream 重點說明 ,並做了點補充。

基本說明

  • Stream 是基於 java8 的 lambda 表達式的,如果不清楚 lambda 表達式,可以查看我的上篇文章Lambda 表達式和函數式介面快速理解
  • Stream 把要處理的元素看做一種流,流在管道中傳輸,可以在管道的節點上處理數據,包含過濾,去重,排序,映射,聚合,分組等。
  • Stream 分為中間操作和後期操作,中期操作會形成一個新的 Stream ,但不會馬上對數據進行處理,到後期操作時,再遍歷整個集合;可以沒有中期操作直接後期操作。

創建流的方式

  • 使用 java.util.Collection 介面的默認方法 stream 或者 parallelStream
  • 使用 java.util.Arrays 的方法 stream 將數組變成流

中期操作和後期操作

Stream 分為中間操作和後期操作,中期操作會形成一個新的 Stream ,但不會馬上對數據進行處理,到後期操作時,再遍歷整個集合;可以沒有中期操作直接後期操作。

中期操作

  • map 和 map 之類的,用於映射一種類型到另一種類型
  • filter 用於過濾掉一些不符合要求的元素
  • distinct 用於排重
  • sorted 用於排序
  • flatMap 用於將流扁平化

後期操作

forEach,collect,reduce,anyMatch,allMatch,noneMatch,findFirst 等;

其中屬 collect 最為常用,還有一個專門用於 collect 的 Collectors 類,可以用於將集合轉成 List,Set,Map 等

程式碼示例

數據準備

  1. 準備一個用於下面例子測試的對象
import lombok.AllArgsConstructor;  import lombok.Data;  import lombok.NoArgsConstructor;    @Data  @NoArgsConstructor  @AllArgsConstructor  public class Vehicle {      //車架號      private String vin;      // 車主手機號      private String phone;      // 車主姓名      private String name;      // 所屬車租車公司      private Integer companyId;      // 個人評分      private Double score;      //安裝的設備列表imei,使用逗號分隔      private String deviceNos;  }
  1. 準備一些車輛數據
static List<Vehicle> vehicles = new ArrayList<>();    @Before  public void init(){      List<String> imeis = new ArrayList<>();      for (int i = 0; i <5 ; i++) {          List<String> singleVehicleDevices = new ArrayList<>();          for (int j = 0; j < 3; j++) {              String imei = RandomStringUtils.randomAlphanumeric(15);              singleVehicleDevices.add(imei);          }          imeis.add(StringUtils.join(singleVehicleDevices,','));      }      vehicles.add(new Vehicle("KPTSOA1K67P081452","17620411498","9420",1,4.5,imeis.get(0)));      vehicles.add(new Vehicle("KPTCOB1K18P057071","15073030945","張玲",2,1.4,imeis.get(1)));      vehicles.add(new Vehicle("KPTS0A1K87P080237","19645871598","sanri1993",1,3.0,imeis.get(2)));      vehicles.add(new Vehicle("KNAJC526975740490","15879146974","李種",1,3.9,imeis.get(3)));      vehicles.add(new Vehicle("KNAJC521395884849","13520184976","袁紹",2,4.9,imeis.get(4)));  }

forEach 遍歷Collection 數據

vehicles.forEach(vehicle -> System.out.println(vehicle));    //這樣就可以遍歷列印  vehicles.forEach(System.out::println);

forEach 遍歷 Map 數據

Map<String,Integer> map = new HashMap<>();  map.put("a",1);map.put("b",2);map.put("c",3);    map.forEach((k,v) -> System.out.println("key:"+k+",value:"+v));

filter 數據過濾

// 去掉評分為 3 分以下的車  List<Vehicle> collect = vehicles.stream().filter(vehicle -> vehicle.getScore() >= 3).collect(Collectors.toList());

map 對象映射

對一個 List<Object> 大部分情況下,我們只需要列表中的某一列,或者需要把裡面的每一個對象轉換成其它的對象,這時候可以使用 map 映射,示例:

// 取出所有的車架號列表   List<String> vins = vehicles.stream().map(Vehicle::getVin).collect(Collectors.toList());

groupBy 按照某個屬性進行分組

// 按照公司 Id 進行分組  Map<Integer, List<Vehicle>> companyVehicles = vehicles.stream().collect(Collectors.groupingBy(Vehicle::getCompanyId));    // 按照公司分組求司機打分和  Map<Integer, Double> collect = vehicles.stream().collect(Collectors.groupingBy(Vehicle::getCompanyId, Collectors.summingDouble(Vehicle::getScore)));

sort 按照某個屬性排序 ,及多列排序

// 單列排序  vehicles.sort((v1,v2) -> v2.getScore().compareTo(v1.getScore()));    // 或使用 Comparator 類來構建比較器,流處理不會改變原列表,需要接收返回值才能得到預期結果   List<Vehicle> collect = vehicles.stream().sorted(Comparator.comparing(Vehicle::getScore).reversed()).collect(Collectors.toList());    // 多列排序,score 降序,companyId 升序排列  List<Vehicle> collect = vehicles.stream().sorted(Comparator.comparing(Vehicle::getScore).reversed()                  .thenComparing(Comparator.comparing(Vehicle::getCompanyId)))                  .collect(Collectors.toList());

flatMap 扁平化數據處理

// 查出所有車綁定的所有設備  List<String> collect = vehicles.stream().map(vehicle -> {      String deviceNos = vehicle.getDeviceNos();      return StringUtils.split(deviceNos,',');  }).flatMap(Arrays::stream).collect(Collectors.toList());

flatMap 很適合 List<List>List<object []> 這種結構,可以當成一個列表來處理;像上面的設備列表,在資料庫中存儲的結構就是以逗號分隔的數據,而車輛列表又是一個列表數據。

將 List 數據轉成 Map

// 將 List 轉成 Map ; key(vin) == > Vehicle  Map<String, Vehicle> vinVehicles = vehicles.stream().collect(Collectors.toMap(Vehicle::getVin, vehicle -> vehicle));

mapReduce 數據處理

// 對所有司機的總分求和  Double reduce = vehicles.stream().parallel().map(Vehicle::getScore).reduce(0d, Double::sum);

求百分比

// 總的分值  Double totalScore = vehicles.stream().parallel().map(Vehicle::getScore).reduce(0d, Double::sum);    // 查看每一個司機占的分值比重  List<String> collect = vehicles.stream()      .mapToDouble(vehicle -> vehicle.getScore() / totalScore)      .mapToLong(weight -> (long) (weight * 100))      .mapToObj(percentage -> percentage + "%")      .collect(Collectors.toList());

anyMatch/allMatch/noneMatch 匹配操作

  • anyMatch 只要有元素匹配,即返回真
  • allMatch 要求所有的元素都匹配
  • noneMatch 要求沒有一個元素匹配
// 檢查是否有姓李的司機 true  boolean anyMatch = vehicles.stream().anyMatch(vehicle -> vehicle.getName().startsWith("李"));    // 檢查是否所有司機的評分都大於 3 分 false  boolean allMatch = vehicles.stream().allMatch(vehicle -> vehicle.getScore() > 3);    // 檢查是否有 3 公司的特務 true  boolean noneMatch = vehicles.stream().noneMatch(vehicle -> vehicle.getCompanyId() == 3);

一點小推廣

創作不易,希望可以支援下我的開源軟體,及我的小工具,歡迎來 gitee 點星,fork ,提 bug 。

Excel 通用導入導出,支援 Excel 公式
部落格地址:https://blog.csdn.net/sanri1993/article/details/100601578
gitee:https://gitee.com/sanri/sanri-excel-poi

使用模板程式碼 ,從資料庫生成程式碼 ,及一些項目中經常可以用到的小工具
部落格地址:https://blog.csdn.net/sanri1993/article/details/98664034
gitee:https://gitee.com/sanri/sanri-tools-maven