Java 8 中新增的特性旨在幫助程式設計師寫出更好的程式碼,其中對核心類庫的改進是很關鍵的一部分,也是本章的主要內容。對核心類庫的改進主要包括集合類的 API 和新引入的流(Stream),流使程式設計師得以站在更高的抽象層次上對集合進行操作。下面將介紹stream流的用法。
場景:現在有一個公司,公司部門有一級部門,二級部門甲和二級部門乙(其中二級部門甲和二級部門乙是一級部門的子部門),
一級部門下面有有001號員工小明,二級部門甲下面有002號員工小剛和003號員工小李,二級部門乙有002號員工小剛和004號員工小張,其中員工id是唯一的,員工小剛既是二級部門甲又是二級部門乙的員工。程式碼展示如下:
public class LambdaUseCase {
static List<Department> departmentList;
static {
// 一級部門,部門人員有001號員工小明
Department departmentOne = new Department("一級部門", 1,10000L,11000L,
Arrays.asList(new Person("001","小明",22)));
// 二級部門甲,部門人員有002號員工小剛和003號員工小李
Department departmentTwoFirst = new Department("二級部門甲", 2,8000L,13000L,
Arrays.asList(new Person("002","小剛",23),
new Person("003","小李",32)));
// 二級部門乙,部門人員有002號員工小剛和004號員工小張
Department departmentTwoSecond = new Department("二級部門已", 2,7500L,15000L,
Arrays.asList(new Person("002","小剛",23),
new Person("004","小張",34)));
departmentList = Arrays.asList(departmentOne,departmentTwoFirst,departmentTwoSecond);
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
class Department {
// 部門名
private String departmentName;
// 部門等級
private Integer departmenRank;
// 部門薪資(單位分)
private Long departSalary;
// 部門日盈利
private Long departProfit;
// 部門人員集合
private List<Person> persons;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
//重寫equal和hashcode方法,用於資料去重
@EqualsAndHashCode
class Person {
// 人員id
private String personId;
// 人員姓名
private String personName;
// 人員年齡
private Integer personAge;
}
單列集合: 集合物件.stream()
建立流
departmentList.stream();
陣列:Arrays.stream(陣列)
或者使用Stream.of
來建立
Integer[] arr = {1,2,3,4,5};
Stream<Integer> stream = Arrays.stream(arr);
Stream<Integer> stream2 = Stream.of(arr);
Stream.of(1,2,3,4);
雙列集合:轉換成單列集合後再建立
Map<String, Integer> map = new HashMap<>();
map.put("a", 1);
map.put("b", 2);
map.put("c", 3);
Stream<Map.Entry<String, Integer>> stream = map.entrySet().stream();
如果有一個函數可以將一種型別的值轉換成另外一種型別,map 操作就可以使用該函數,將一個流中的值轉換成一個新的流
需求:公司今年收益提供,決定把所有部門的平均薪資提升1000。
List<Department> departments = departmentList.stream().map(e -> {
e.setDepartSalary(e.getDepartSalary() + 1000);
return e;
}).collect(Collectors.toList());
對流中的元素進行過濾,篩選出符合過濾條件的資料
需求:需要篩選出部門平均薪資大於等於8000的資料。
List<Department> departments = departmentList.stream()
.filter(e -> e.getDepartSalary() >= 8000)
.collect(Collectors.toList());
flatMap 方法可用 Stream 替換值,然後將多個 Stream 連線成一個 Stream
需求:把公司所有的人員收集到一個集合中。
不使用flatMap,你可能會這麼做,迴圈裡套迴圈,看上去不太美觀。
Set<Person> personSet = new HashSet<>();
departmentList.stream().forEach(e->{
e.getPersons().stream().forEach(person->{
personSet.add(person);
});
});
使用flatMap寫法。
Set<Person> personSet = departmentList.stream()
.flatMap(e -> e.getPersons().stream())
.collect(Collectors.toSet());
去除流中的重複元素
需求:將公司所有的人員統計出來,這回不使用Set去重,注意:002號員工即在二級部門甲又在二級部門乙
List<Person> personSet = departmentList.stream()
.flatMap(e -> e.getPersons().stream())
.distinct()
.collect(Collectors.toList());
注意:distinct方法是依賴Object的equals方法來判斷是否是相同物件的。所以需要注意重寫equals方法。
對流中的元素進行排序
需求: 按薪資從低到高將部門列出來。
List<Department> departments = departmentList.stream()
.sorted((o1, o2) -> o1.getDepartSalary().compareTo(o2.getDepartSalary()))
.collect(Collectors.toList());
注意:如果呼叫空參的sorted()方法,需要流中的元素是實現了Comparable。
可以設定流的最大長度,超出的部分將被拋棄
需求:按薪資從低到高將部門列出來,並且找出薪資最低的倆個部門
List<Department> departments = departmentList.stream()
.sorted(Comparator.comparing(Department::getDepartSalary))
.limit(2)
.collect(Collectors.toList());
跳過流中的前n個元素,返回剩下的元素
需求:按薪資從低到高將部門列出來,並且忽略薪資最低的部門
List<Department> departments = departmentList.stream()
.sorted(Comparator.comparing(Department::getDepartSalary))
.skip(1)
.collect(Collectors.toList());
對流中的元素進行遍歷操作
需求:要求輸出全部部門的名稱
departmentList.stream()
.forEach(e -> System.out.println(e.getDepartmentName()));
可以用來獲取當前流中元素的個數。
需求:統計部門的格式
long count = departmentList.stream()
.count();
Stream 上常用的操作之一是求最大值和最小值
需求:分別找出部門裡面薪資最高的部門和最低的部門
Department maxDepartment = departmentList.stream()
.max(Comparator.comparing(Department::getDepartSalary))
.get();
Department minDepartment = departmentList.stream()
.min(Comparator.comparing(Department::getDepartSalary))
.get();
將當前的流轉換為一個集合
需求:獲得一個儲存所有部門名字的集合
List<String> departNameList = departmentList.stream()
.map(Department::getDepartmentName)
.collect(Collectors.toList());
需求:獲得部門所有人的姓名(重名的忽略)
Set<String> personNameList = departmentList.stream()
.flatMap(e -> e.getPersons().stream())
.map(Person::getPersonName)
.collect(Collectors.toSet());
需求:獲得部門名稱當做key,員工當做value的Map
Map<String, List<Person>> collect = departmentList.stream()
.collect(Collectors.toMap(Department::getDepartmentName, Department::getPersons));
需求:將所有的部門名字連起來,並且字首是【,字尾是】,分隔符是,
departmentList.stream()
.map(Department::getDepartmentName)
.collect(Collectors.joining(",","[","]"));
接受一個流,並將其分成兩部分,使用 Predicate 物件判斷一個元素應該屬於哪個部分,並根據布林值返回一個 Map 到列表。
需求:將一級部門和其他級別的部門分離出來
Map<Boolean, List<Department>> collect = departmentList
.stream()
.collect(Collectors.partitioningBy(e -> e.getDepartmenRank() == 1));
接受一個分類函數,用來對資料分組
需求:將部門按部門的階級分組
Map<Integer, List<Department>> collect = departmentList
.stream()
.collect(Collectors.groupingBy(Department::getDepartmenRank));
各種收集器已經很強大了,但如果將它們組合起來,會變得更強大。
需求:統計各個階級部門的平均薪資
Map<Integer, Double> collect = departmentList.stream()
.collect(Collectors.groupingBy(Department::getDepartmenRank,Collectors.averagingLong(Department::getDepartSalary)));
這個例子中用到了第二個收集器,用以收集最終結果的一個子集。這些收集器叫作下游收集器。收集器是生成最終結果的一劑配方,下游收集器則是生成部分結果的配方,主收集器中會用到下游收集器。這種組合使用收集器的方式,使得它們在 Stream 類庫中的作用更加強大。
對流中的資料按照指定的方式計算出結果
需求:計算出部門總的日盈利
Long sum = departmentList.stream()
.map(Department::getDepartProfit)
.reduce(0L, (result, element) -> result + element);
可以用來判斷是否有任意符合匹配條件的元素,結果為boolean型別。
需求:判斷部門中有沒有薪資大於9000的部門
boolean b = departmentList.stream()
.anyMatch(e -> e.getDepartSalary() > 9000L);
可以用來判斷是否都符合匹配條件,結果為boolean型別。如果都符合結果為true,否則結果為false。
需求:判斷部門薪資是不是都大於9000
boolean b = departmentList.stream()
.allMatch(e -> e.getDepartSalary() > 9000L);
可以判斷流中的元素是否都不符合匹配條件。如果都不符合結果為true,否則結果為false
需求:判斷是不是所有的部門薪資都大於9000
boolean b = departmentList.stream()
.noneMatch(e -> e.getDepartSalary() > 9000L);
獲取流中的任意一個元素。該方法沒有辦法保證獲取的一定是流中的第一個元素。
需求:找到任意一個部門
Department department = departmentList
.stream()
.findAny()
.get();
獲取流中的第一個元素。
需求:獲得薪資最低的一個部門
Department department = departmentList
.stream()
.sorted(Comparator.comparing(Department::getDepartSalary))
.findFirst()
.get();
在 Java 中,有一些相伴的型別,比如 int 和 Integer——前者是基本型別,後者是裝箱型別。基本型別內建在語言和執行環境中,是基本的程式構建模組;而裝箱型別屬於普通的 Java 類,只不過是對基本型別的一種封裝。由於裝箱型別是物件,因此在記憶體中存在額外開銷。比如,整型在記憶體中佔用4 位元組,整型物件卻要佔用 16 位元組。這一情況在陣列上更加嚴重,整型陣列中的每個元素只佔用基本型別的記憶體,而整型物件陣列中,每個元素都是記憶體中的一個指標,指向 Java堆中的某個物件。在最壞的情況下,同樣大小的陣列,Integer[] 要比 int[] 多佔用 6 倍記憶體。
將基本型別轉換為裝箱型別,稱為裝箱,反之則稱為拆箱,兩者都需要額外的計算開銷。對於需要大量數值運算的演演算法來說,裝箱和拆箱的計算開銷,以及裝箱型別佔用的額外記憶體,會明顯減緩程式的執行速度。
為了減小這些效能開銷,Stream 類的某些方法對基本型別和裝箱型別做了區分。高階函數 mapToLong 和其他類似函數即為該方面的一個嘗試。在 Java 8 中,僅對整型、長整型和雙浮點型做了特殊處理,因為它們在數值計算中用得最多,特殊處理後的系統效能提升效果最明顯。
如有可能,應儘可能多地使用對基本型別做過特殊處理的方法,進而改善效能。這些特殊的 Stream 還提供額外的方法,避免重複實現一些通用的方法,讓程式碼更能體現出數值計算的意圖。
需求:需要計算出公司部門利潤的平均值,最大值,最小值,以及利潤總和。
LongSummaryStatistics longSummaryStatistics = departmentList
.stream()
.mapToLong(Department::getDepartProfit)
.summaryStatistics();
System.out.println("利潤最大值"+longSummaryStatistics.getMax());
System.out.println("利潤最小值"+longSummaryStatistics.getMin());
System.out.println("利潤平均值"+longSummaryStatistics.getAverage());
System.out.println("利潤總和"+longSummaryStatistics.getSum());
這些統計值在所有特殊處理的 Stream,如 DoubleStream、LongStream 中都可以得出。如無需全部的統計值,也可分別呼叫 min、max、average 或 sum 方法獲得單個的統計值,同樣,三種基本型別對應的特殊 Stream 也都包含這些方法。
並 行 化 操 作 流 只 需 改 變 一 個 方 法 調 用。 如 果 已 經 有 一 個 Stream 對 象, 調 用 它 的parallel 方法就能讓其擁有並行操作的能力。如果想從一個集合類建立一個流,呼叫parallelStream 就能立即獲得一個擁有並行能力的流。
需求:並行的統計公司的所有的人員。
Set<Person> collect = departmentList.stream()
.parallel()
.flatMap(e -> e.getPersons().stream()).collect(Collectors.toSet());
Set<Person> collect1 = departmentList.parallelStream()
.flatMap(e -> e.getPersons().stream()).collect(Collectors.toSet());
在底層,並行流還是沿用了 fork/join 框架。fork 遞迴式地分解問題,然後每段並行執行,最終由 join 合併結果,返回最後的值。