- Stream
- 寫在前面
- Stream是什麼
- Stream操作的三個步驟
- 1. 建立Stream
- 2. 中間操作
- 3. 終止操作
- 3.1 查詢與匹配
- 3.2 歸約
- 3.3 收集
- 3.3.1 toList() 將流轉換成List
- 3.3.2 toSet() 將流轉換成Set
- 3.3.3 toCollection() 將流轉換成其他型別的集合
- 3.3.4 counting()計算流中元素個數
- 3.3.5 averagingDouble(...)計算流中元素Double型別的平均值
- 3.3.6 summingDouble(...)計算流中元素Double型別的總和
- 3.3.7 minBy(...)根據比較器選擇最小值
- 3.3.8 maxBy(...)根據比較器選擇最小值
- 3.3.9 groupingBy(...)根據某屬性的值對流分組,屬性為K,結果為V
- 3.3.10 partitioningBy(...)根據True或false進行分割槽
- 3.3.12 joining(...)連線流中每個字串
Stream
寫在前面
Java8中有兩大最為重要的改變:
- Lambda表示式
- Stream API(java.util.stream.*)
Stream是Java8中處理集合的關鍵抽象概念,它可以指定你希望對集合進行的操作,可以執行非常複雜的查詢、過濾、對映資料等操作。使用Stream API對集合資料進行操作,就類似於使用SQL執行資料庫查詢。也可以使用Stream API來並行執行操作。簡而言之,Stream API提供了一種高效且易於使用的處理資料的方式。
Stream是什麼
流是資料渠道,用於運算元據源(集合、陣列等)所生成的元素序列。
即流這個資料渠道,在資料傳輸過程中,對資料來源做一系列流水線式的中間操作,然後產生一個新的流,這個流不會改變原來的流
集合講的是資料,流講的是計算
注意
- Stream 自己不會儲存元素。
- Stream 不會改變源物件。相反,他們會返回一個持有結果的新Stream。
- Stream 操作是延遲執行的。這意味著他們會等需要結果的時候才執行。
Stream操作的三個步驟
1. 建立Stream
1.1 通過Collection集合
通過Collection系列集合提供的Stream()序列流或parallelStream()並行流。
其中,Java8中的Collection介面擴充套件了,提供了兩個獲取流的方法:
- 返回一個順序流
/*
* @since 1.8
*/
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
- 返回一個並行流
/*
* @since 1.8
*/
default Stream<E> parallelStream() {
return StreamSupport.stream(spliterator(), true);
}
下面我們寫個通過Collection系列集合流建立流的例子。
@Test
public void test01(){
//通過Collection系列集合提供的Stream()或parallelStream()
List<String> list = new ArrayList<>();
Stream<String> stream = list.stream();
Stream<String> stringStream = list.parallelStream();
}
1.2 通過陣列Arrays
//通過陣列Arrays 中的靜態方法stream()獲取陣列
Employee[] employees = new Employee[10];
Stream<Employee> stream2 = Arrays.stream(employees);
通過Stream流中的of()方法來建立
//通過Stream類中的靜態方法of()
Stream<String> stream3 = Stream.of("aa", "bb", "cc", "dd");
1.3 建立無限流
建立無限流的兩種方法:迭代、生成
我們先來看一下迭代Stream.iterate()方法的定義
我們再來看一下生成的方式, Stream.generate()這裡需要一個供給型的引數
再來寫下建立無限流的例子
//建立無限流
//迭代 Stream.iterate()傳倆引數,第一個是種子即起始值,第二個引數是Lambda表示式即對起始值進行的操作,
//這裡是生成偶數
Stream<Integer> stream4 = Stream.iterate(0, (x) -> x + 2);
//無限流就是沒有限制,需要一個終止操作限制
stream4.limit(4).forEach(System.out::println);
//生成的方式
Stream<Double> generate5 = Stream.generate(() -> Math.random());
generate5.limit(5).forEach(System.out::println);
執行結果
0
2
4
6
0.3204608858702849
0.495403965457605
0.9188968488509007
0.18726624455121932
0.3791774193868236
2. 中間操作
一箇中間操作鏈,對資料來源的資料進行處理。
多箇中間操作可以連線起來形成一個流水線,除非流水線上觸發了終止操作,否則中間操作不會執行任何的處理,而在終止操作時一次性處理,稱為“惰性求值”或者“延遲載入”。
2.1 篩選與切片
方法 | 描述 |
---|---|
filter(Predicate p) | 接收Lambda,從流中排除某些元素 |
distinct() | 篩選,通過流所生成元素的hashCode()和equal()去除重複元素 |
limit(long maxSize) | 截斷流,使其元素不超過給定數量 |
skip(long n) | 跳過元素,返回一個扔掉了前n個元素的流。若流中元素不足n個,則返回一個空流。與limit(n)互補 |
下面我們一個個來分析:
2.1.1 filter(Predicate p)
接收Lambda,從流中排除某些元素
@Test
public void test02(){
List<Employee> employees = Arrays.asList(
new Employee("張三",68,9000),
new Employee("李四",38,8000),
new Employee("王五",50,4000),
new Employee("趙六",18,3000),
new Employee("田七",8,1000));
//中間操作,不會執行任何操作
//filter(Predicate p) 接收Lambda,從流中排除某些元素
Stream<Employee> employeeStream = employees.stream().filter((e) -> {
System.out.println("StreamAPI的中間操作");
return e.getAge() > 35;
});
//終止操作 當只有中間操作時執行是沒有結果的,因為只有執行終止操作以後所有的中間操作才一次性全部處理,即惰性求值
employeeStream.forEach(System.out::println);
}
執行結果
StreamAPI的中間操作
Employee{name='張三', age=68, salary=9000.0}
StreamAPI的中間操作
Employee{name='李四', age=38, salary=8000.0}
StreamAPI的中間操作
Employee{name='王五', age=50, salary=4000.0}
StreamAPI的中間操作
StreamAPI的中間操作
從執行結果可以看出,迭代操作不是我們做的,是由Stream API 幫我們完成的,再也不需要我們自己完成這個迭代操作了,這也叫內部迭代。與內部迭代相對應的是外部迭代,也就是我們自己寫的迭代。
//外部迭代,我們自己寫的迭代
@Test
public void test03(){
Iterator<Employee> iterator = employees.iterator();
if(iterator.hasNext()){
System.out.println(iterator.next());
}
}
2.1.2 limit(long maxSize)
limit(),下面我們過濾公司中薪資大於5000的僱員之後,獲取其中前2個
//limit()過濾公司中薪資大於5000的僱員之後,獲取其中前2個僱員的資訊
@Test
public void test04(){
employees.stream()
.filter((e) -> {
System.out.println("===短路===");
return e.getSalary()>5000;
})
.limit(2)
.forEach(System.out::println);
}
執行結果
從執行結果我們看出,迭代操作只執行了兩次,也就是說只要找到滿足條件的結果之後,就不再進行迭代了,這個過程就叫“短路”。所以說它也可以提高效率,跟我們之前學的短路&&和||有點類似。
2.1.3 skip(long n)
扔掉也即跳過前幾個元素
List<Employee> employees = Arrays.asList(
new Employee("張三",68,9000),
new Employee("李四",38,8000),
new Employee("王五",50,4000),
new Employee("趙六",18,3000),
new Employee("田七",8,1000));
//skip()過濾公司中薪資大於5000的僱員之後,跳過前2個僱員的資訊
@Test
public void test05(){
employees.stream()
.filter((e) -> e.getSalary()>2000)
.skip(2)
.forEach(System.out::println);
}
執行結果,跳過了8000和9000的工資
Employee{name='王五', age=50, salary=4000.0}
Employee{name='趙六', age=18, salary=3000.0}
2.1.4 distinct()
我們首先給employees集合中新增幾個重複的元素趙六
@Test
public void test06(){
List<Employee> employees1 = Arrays.asList(
new Employee("張三",68,9000),
new Employee("李四",38,8000),
new Employee("王五",50,4000),
new Employee("趙六",18,3000),
new Employee("趙六",18,3000),
new Employee("趙六",18,3000),
new Employee("趙六",18,3000),
new Employee("田七",8,1000));
employees1.stream()
.filter((e) -> e.getSalary()>2000)
.distinct()
.forEach(System.out::println);
}
執行結果
我們發現結果並沒有去重,因為,他是通過流所生成元素的hashCode()和equals()來去除重複元素的。所以,要想去除重複元素,必須得重寫Employee實體類中的hashCode()和equals()這兩個方法。
重寫後的Employee如下
package com.cqq.java8.lambda;
import java.util.Objects;
/**
* @author caoqianqian
* @Description:
* @date 2021/3/6 9:24
*/
public class Employee {
private String name;
private int age;
private double salary;
public Employee(String name, int age, double salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
public Employee() {
}
public Employee(int age) {
this.age = age;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
", salary=" + salary +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return age == employee.age &&
Double.compare(employee.salary, salary) == 0 &&
Objects.equals(name, employee.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age, salary);
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
}
我們再執行一下結果,已經是去重了的。
2.2 對映
方法 | 描述 |
---|---|
map(Function f) | 接收一個函式作為引數,該函式會被應用到每個元素上,並將其對映成一個新的元素 |
mapToDouble(ToDoubleFunction f) | 接收一個函式作為引數,該函式會被應用到每個元素上,並將其對映成一個新的DoubleStream |
mapToInt(ToIntFunction f) | 接收一個函式作為引數,該函式會被應用到每個元素上,並將其對映成一個新的IntStream |
mapToLong(ToLongFunction f) | 接收一個函式作為引數,該函式會被應用到每個元素上,並將其對映成一個新的LongStream |
flatMap(Function f) | 接收一個函式作為引數,將流中的每個值都換成另一個流,然後把左右流連成一個流 |
2.2.1 map(Function f)
接收Lambda,將元素轉換成其他形式或提取資訊。接收一個函式作為引數,該函式會被應用到每個元素上,並將其對映成一個新的元素
@Test
public void test07(){
//將字母轉大寫
List<String> list = Arrays.asList("aa","bb","cc");
list.stream()
.map(str -> str.toUpperCase())
.forEach(System.out::println);
//提取員工名字
employees.stream()
.map((e) -> e.getName())
.forEach(System.out::println);
}
執行結果
AA
BB
CC
張三
李四
王五
趙六
田七
2.2.2 flatMap(Function f)
接收一個函式作為引數,將流中的每個值都換成另一個流,然後把所有流連成另一個流。
我們先來寫一個方法來解析字串,並把字串中的一個一個的字元給單獨提取出來,放到集合中。
public static Stream<Character> filterCharacter(String str){
List<Character> list = new ArrayList<>();
for (Character c:str.toCharArray()) {
list.add(c);
}
return list.stream();
}
再來看這個測試例子
//faltMap
@Test
public void test08(){
//提取list裡的一個個字串的每一個字元
List<String> list = Arrays.asList("aa","bb","cc");
//通過map提取到的是一個stream流裡還是stream流
Stream<Stream<Character>> streamStream = list.stream()
.map(TestStreamAPI::filterCharacter);
//這裡得巢狀遍歷才可以迴圈出每個字串的結果
streamStream.forEach(
s -> s.forEach(System.out::println)
);
//通過flatMap提取到的是一個stream流
Stream<Character> streamStream2 = list.stream()
.flatMap(TestStreamAPI::filterCharacter);
//這樣就不用了巢狀遍歷了
streamStream2.forEach(System.out::println);
}
其實map方法就相當於Collaction的add方法,如果add的是個集合的話就會變成二維陣列,而flatMap 的話就相當於Collaction的addAll方法,引數如果是集合的話,只是將2個集合合併,而不是變成二維陣列。
2.3 排序
方法 | 描述 |
---|---|
sorted() | 產生一個新流,其中按自然順序排序 |
sorted(Comparator comp) | 產生一個新流,其中按比較器順序排序 |
下面我們編寫測試例子,首先我們先建立一個員工的集合:
List<Employee> employees = Arrays.asList(
new Employee("張三",68,9000),
new Employee("李四",38,8000),
new Employee("王五",50,4000),
new Employee("趙六",18,3000),
new Employee("田七",8,1000));
用sort() 自然排序實現一串字元list的自然排序。用sorted(Comparator comp) 比較器排序,實現員工按年齡排序,如果年齡相等按姓名排。
//排序
//sort() 自然排序
//sorted(Comparator comp) 比較器排序
@Test
public void test09(){
//sort() 將字母按自然醒順序排序
List<String> list = Arrays.asList("bb","aa","cc");
list.stream()
.sorted()
.forEach(System.out::println);
//sorted(Comparator comp) 比較器排序 按年齡排序,如果年齡相等按姓名排
employees.stream()
.sorted((e1,e2) -> {
if(e1.getAge()== e2.getAge()){
return e1.getName().compareTo(e2.getName());
}else {
return Integer.compare(e1.getAge(),e2.getAge());
}
})
.forEach(System.out::println);
}
執行結果
aa
bb
cc
Employee{name='田七', age=8, salary=1000.0}
Employee{name='趙六', age=18, salary=3000.0}
Employee{name='李四', age=38, salary=8000.0}
Employee{name='王五', age=50, salary=4000.0}
Employee{name='張三', age=68, salary=9000.0}
3. 終止操作
一個終止操作,執行中間操作鏈,併產生結果。
即終止操作會從流的流水線生成結果,其結果可以是任何不是流的值,例如List、Integer、甚至是void。
3.1 查詢與匹配
方法 | 描述 |
---|---|
allMatch(Predicate p) | 檢查是否匹配所有元素 |
anyMatch(Predicate p) | 檢查是否至少匹配一個元素 |
noneMatch(Predicate p) | 檢查是否沒有匹配所有元素 |
findFirst() | 返回第一個元素 |
findAny() | 返回當前流中的任意元素 |
count() | 返回流中元素的總個數 |
max(Comparator c) | 返回流中最大值 |
min(Comparator c) | 返回流中最小值 |
forEach(Consumer c) | 內部迭代(使用Collection介面需要使用者去做迭代,成為外部迭代。相反,Stream API使用內部迭代--它幫你把迭代做好了) |
3.1.1 allMatch(Predicate p)檢查是否匹配所有元素
下面寫一個例子,檢查公司中所有員工是否處於空閒狀態。
@Test
public void test10(){
List<Employee> employees1 = Arrays.asList(
new Employee("張三",68,9000, Employee.Status.FREE),
new Employee("李四",38,8000,Employee.Status.BUSY),
new Employee("王五",50,4000,Employee.Status.VOCATION),
new Employee("趙六",18,3000,Employee.Status.BUSY),
new Employee("田七",8,1000,Employee.Status.FREE));
boolean b = employees1.stream()
.allMatch(e -> e.getStatus().equals(Employee.Status.FREE));
System.out.println("===allMatch===="+b);
}
Employee 的實體類中加個狀態和對應新增這個引數的構造方法
package com.cqq.java8.lambda;
import java.util.Objects;
public class Employee {
private String name;
private int age;
private double salary;
private Status status;
public Employee(String name, int age, double salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
public Employee(String name, int age, double salary,Status status) {
this.name = name;
this.age = age;
this.salary = salary;
this.status = status;
}
public Employee() {
}
public Employee(int age) {
this.age = age;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
", salary=" + salary +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return age == employee.age &&
Double.compare(employee.salary, salary) == 0 &&
Objects.equals(name, employee.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age, salary);
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public Status getStatus() {
return status;
}
public void setStatus(Status status) {
this.status = status;
}
public void setAge(int age) {
this.age = age;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public enum Status{
FREE,
BUSY,
VOCATION;
}
}
3.1.2 anyMatch(Predicate p)檢查是否至少匹配一個元素
//anyMatch
@Test
public void test11(){
boolean c = employees1.stream()
.anyMatch(e -> e.getStatus().equals(Employee.Status.FREE));
System.out.println("===anyMatch===="+c);
}
3.1.3 noneMatch(Predicate p)檢查是否沒有匹配所有元素
//noneMatch
@Test
public void test12(){
boolean d = employees1.stream()
.noneMatch(e -> e.getStatus().equals(Employee.Status.FREE));
System.out.println("===anyMatch===="+d);
}
3.1.4 findFirst()返回第一個元素
//findFirst
@Test
public void test13(){
Optional<Employee> first = employees1.stream()
.sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()))
.findFirst();
System.out.println(first.get());
}
注意:上面findFirst返回的是一個Optional物件,它將我們的Employee封裝了一層,這是為了避免了空指標。而且這個物件為我們提供了orElse方法,也就是當我們拿到的這個物件為空的時候,我們可以傳入一個新的物件去代替它。
3.1.5 findAny()返回當前流中任意元素
//findAny
@Test
public void test14(){
Optional<Employee> first = employees1.stream()
.sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()))
.findAny();
System.out.println(first.get());
}
3.1.6 count()返回流中的總個數
//count
@Test
public void test15(){
long count = employees1.stream()
.count();
System.out.println(count);
}
3.1.7 max()返回流中最大的元素
//max
@Test
public void test16(){
//兩種方法
Optional<Employee> max1 = employees1.stream()
.max((e1,e2) -> Integer.compare(e1.getAge(),e2.getAge()));
Optional<Employee> max2 = employees1.stream()
.max(Comparator.comparingInt(Employee::getAge));
System.out.println(max1);
}
3.1.8 min()返回流中最小的元素
//min
@Test
public void test17(){
//兩種方法
Optional<Employee> max1 = employees1.stream()
.min((e1,e2) -> Integer.compare(e1.getAge(),e2.getAge()));
Optional<Employee> max2 = employees1.stream()
.min(Comparator.comparingInt(Employee::getAge));
System.out.println(max1);
}
3.1.9 forEach(Consumer c)
這個我們很熟悉了,每個測試例子都迴圈列印輸出
3.2 歸約
方法 | 描述 |
---|---|
reduce(T identity,Binaryoperator b) | 可以將流中元素反覆結合在一起,得到一個值。返回T |
reduce(Binaryoperator b) | 可以將流中元素反覆結合在一起,得到一個值。返回Optional |
3.2.1 reduce(T identity,Binaryoperator b)
//reduce()
@Test
public void test18(){
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
//首先,需要傳一個起始值,然後,傳入的是一個二元運算。
Integer count = list.stream().reduce(0, (x, y) -> x + y);
System.out.println(count);
}
reduce 歸約,把流中的元素按照(x, y) -> x + y的形式進行累加操作。首先0是一個起始值,然後從流中取出第一個元素1作為y進行累加即結果為1,再將這個結果1作為x,再從流中取出一個元素2作為y進行累加,結果為3,一次類推,反覆結合,最終得到一個新值。
3.2.2 reduce(Binaryoperator b)
//reduce()
@Test
public void test19(){
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
//沒有起始值,則有可能結果為空,所以返回的值會被封裝到Optional中。
Optional<Integer> reduce = list.stream().reduce((x, y) -> x + y);
//另一種寫法
Optional<Integer> reduce1 = list.stream().reduce(Integer::sum);
System.out.println(reduce.get());
System.out.println(reduce1.get());
}
沒有起始值,則有可能結果為空,所以返回的值會被封裝到Optional中。
備註:map和reduce的連線通常稱為map-reduce模式,因Google用它來進行網路搜尋而出名。
3.3 收集
方法 | 描述 |
---|---|
collect(Collect c) | 將流轉化為其他形式,接收一個Collector介面的實現,用於給Stream中元素做彙總的方法 |
Collector介面中方法的實現,決定了如何對流執行收集操作(如收集到List、Set及Map中),而且Collectors實用類中提供了許多靜態方法,可以方便的建立常見的收集器例項。Collectors實用類中常用的靜態方法如下表所示:
方法 | 返回型別 | 作用 |
---|---|---|
toList() | List |
把流中的元素收集到List集合中 |
toSet() | Set |
把流中的元素收集到Set集合中 |
toCollection(...) | Collection |
把流中的元素收集到建立好的集合中 |
counting() | Long | 計算流中元素個數 |
averagingDouble(...) | Double | 計算流中元素Double型別的平均值 |
summingDouble(...) | Double | 計算流中元素Double型別的總和 |
maxBy(...) | Optional |
根據比較器選擇最大值 |
minBy(...) | Optional |
根據比較器選擇最小值 |
groupingBy(...) | Map<K,List |
根據某屬性的值對流分組,屬性為K,結果為V |
partitioningBy(...) | Map<Boolean,List |
根據True或false進行分割槽 |
summarizingBy(...) | DoubleSummaryStatistics | 收集流(流中元素為Double型別)的統計值。如:平均值 |
joining(...) | String | 連線流中每個字串 |
3.3.1 toList() 將流轉換成List
//toList()
@Test
public void test20(){
List<Integer> list = employees.stream().map(Employee::getAge).collect(Collectors.toList());
list.forEach(System.out::println);
}
3.3.2 toSet() 將流轉換成Set
//toSet()
@Test
public void test21(){
Set<Integer> collect = employees.stream().map(Employee::getAge).collect(Collectors.toSet());
collect.forEach(System.out::println);
}
3.3.3 toCollection() 將流轉換成其他型別的集合
將流轉換成HashSet
//toCollection()
@Test
public void test22(){
//換成HashSet
HashSet<Integer> collect = employees.stream().map(Employee::getAge).collect(Collectors.toCollection(HashSet::new));
collect.forEach(System.out::println);
}
3.3.4 counting()計算流中元素個數
//counting()
@Test
public void test23(){
//總數
Long collect = employees.stream().collect(Collectors.counting());
System.out.println(collect);
}
3.3.5 averagingDouble(...)計算流中元素Double型別的平均值
//averagingDouble()
@Test
public void test24(){
//平均值
Double collect1 = employees.stream().collect(Collectors.averagingDouble(Employee::getAge));
System.out.println(collect1);
}
3.3.6 summingDouble(...)計算流中元素Double型別的總和
IntSummaryStatistics裡包含{count=, sum=, min=, average=, max=}這幾項需要哪個值,通過get獲取就行
summarizingInt
//summingDouble()
@Test
public void test25(){
//平均值
DoubleSummaryStatistics collect1 = employees.stream().collect(Collectors.summarizingDouble(Employee::getSalary));
IntSummaryStatistics collect = employees.stream().collect(Collectors.summarizingInt(Employee::getAge));
System.out.println(collect);
System.out.println(collect.getSum());
}
再來看下summarizingInt()
//summarizingInt()
@Test
public void test26(){
//平均值 IntSummaryStatistics裡包含{count=5, sum=182, min=8, average=36.400000, max=68}這幾項需要哪個獲取就行
IntSummaryStatistics collect = employees.stream().collect(Collectors.summarizingInt(Employee::getAge));
System.out.println(collect);
//從IntSummaryStatistics裡取sum
System.out.println(collect.getSum());
}
執行結果
IntSummaryStatistics{count=5, sum=182, min=8, average=36.400000, max=68}
182
Collectors.summingLong()同上一樣,傳輸傳入Long型即可。
3.3.7 minBy(...)根據比較器選擇最小值
//minBy()
@Test
public void test28(){
Optional<Employee> min = employees.stream().collect(Collectors.minBy((x, y) -> Integer.compare(x.getAge(), y.getAge())));
System.out.println(min.get());
}
3.3.8 maxBy(...)根據比較器選擇最小值
//maxBy()
@Test
public void test27(){
Optional<Employee> max = employees.stream().collect(Collectors.maxBy((x, y) -> Integer.compare(x.getAge(), y.getAge())));
System.out.println(max.get());
//等價於這種形式
Optional<Employee> max1 = employees.stream().max((x, y) -> Integer.compare(x.getAge(), y.getAge()));
System.out.println(max1.get());
}
3.3.9 groupingBy(...)根據某屬性的值對流分組,屬性為K,結果為V
分組分為一級分組和多級分組,下面例子分別進行了演示:
一級分組的例子
//groupingBy()一級分組
@Test
public void test29(){
Map<Employee.Status, List<Employee>> collect = employees.stream()
.collect(Collectors.groupingBy(Employee::getStatus));
System.out.println(collect);
}
執行結果
{
BUSY=[Employee{name='張三', age=68, salary=9000.0}, Employee{name='李四', age=38, salary=8000.0}, Employee{name='趙六', age=18, salary=3000.0}],
VOCATION=[Employee{name='王五', age=50, salary=4000.0}],
FREE=[Employee{name='田七', age=8, salary=1000.0}]
}
多級分組的例子
//多級分組
@Test
public void test30(){
Map<Employee.Status,Map<String,List<Employee>>> map = employees.stream()
.collect(Collectors.groupingBy(Employee::getStatus,Collectors.groupingBy(e -> {
if(e.getAge()<35){
return "青年";
}else if(e.getAge()<50){
return "中年";
}else {
return "老年";
}
})));
System.out.println(map);
}
執行結果
{
FREE={青年=[Employee{name='田七', age=8, salary=1000.0}]},
BUSY={青年=[Employee{name='趙六', age=18, salary=3000.0}], 老年=[Employee{name='張三', age=68, salary=9000.0}], 中年=[Employee{name='李四', age=38, salary=8000.0}]},
VOCATION={老年=[Employee{name='王五', age=50, salary=4000.0}]}
}
3.3.10 partitioningBy(...)根據True或false進行分割槽
//partitioningBy()
@Test
public void test31(){
Map<Boolean, List<Employee>> map = employees.stream()
.collect(Collectors.partitioningBy(e -> e.getSalary() > 5000));
System.out.println(map);
}
執行結果
{
false=[Employee{name='王五', age=50, salary=4000.0}, Employee{name='趙六', age=18, salary=3000.0}, Employee{name='田七', age=8, salary=1000.0}],
true=[Employee{name='張三', age=68, salary=9000.0}, Employee{name='李四', age=38, salary=8000.0}]
}
3.3.12 joining(...)連線流中每個字串
//joining()
@Test
public void test32(){
String collect = employees.stream().map(Employee::getName)
.collect(Collectors.joining(","));
System.out.println(collect);
}
執行結果
張三,李四,王五,趙六,田七
至此,我們Stream的基本操作差不多就結束了,我們只需要多加練習,熟練使用就能提高效率啦。