1. 程式人生 > >Java8 Streams 讓集合操作飛起來

Java8 Streams 讓集合操作飛起來

前言

接上篇文章 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-ma