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 等
程式碼示例
資料準備
- 準備一個用於下面例子測試的物件
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; }
- 準備一些車輛資料
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