前言
Java 8 (又稱為 jdk 1.8) 是 Java 語言開發的一個主要版本。 Oracle 公司于 2014 年 3 月 18 日釋出 Java 8 ,它支援函數式程式設計,新的 JavaScript 引擎,新的日期 API,新的Stream API 等。
新添加的Stream API(java.util.stream) 把真正的函數式程式設計風格引入到Java中。它可以讓你以一種聲明的方式處理資料,進而寫出高效率、幹淨、簡潔的代碼。
這種風格将要處理的元素集合看作一種流, 流在管道中傳輸, 并且可以在管道的節點上進行處理, 比如篩選, 排序,聚合等。
一、Stream 特性
- 元素是特定類型的對象,形成一個隊列。 Java中的Stream不會存儲元素,而是按需計算按照特定的規則對資料進行計算,一般會輸出結果。
- Stream不會改變資料源,一般情況下會産生一個新的集合或者新值。
- Stream流的來源,可以是集合,數組,I/O channel, 産生器generator 等等。
- Stream具有延遲執行特性,隻有調用終端操作時,中間操作才會執行。中間操作都會傳回流對象本身。 這樣多個操作可以串聯成一個管道,如同流式風格(fluent style)。
- 以前對集合周遊都是通過Iterator或者For-Each的方式, 顯式的在集合外部進行疊代, 這叫做外部疊代。Stream提供了内部疊代的方式, 通過通路者模式(Visitor)實作。
Stream流的操作可以大概分為2種:
- 中間操作:每次操作都傳回流對象本身。
- 終端操作:一個流隻可以進行一次終端操作,即産生一個新的集合或者新值。終端操作結束後流無法再次使用。
二、Stream 建立
在 Java 8 中, Stream可以由集合或數組建立而來,生成的流有2種類型:
- stream() :串行流,由主線程按順序對流執行操作。
- parallelStream():并行流,内部以多線程并行執行的方式對流進行操作,但前提是流中的資料處理沒有順序要求。例如計算集合中的數量之和。如果流種資料量很大,并行流可以加快處理速度。串行流可以通過
方法把順序流轉換成并行流。
parallel()
2.1 用集合建立流
因為集合繼承或實作了接口,而Collection接口定義了
java.util.Collection
和
stream()
方法,是以可通過集合的stream() 和parallelStream()方法建立流。
parallelStream()
// 建立集合
List<String> list = Arrays.asList("張三", "李四", "王五");
// 建立一個串行流
Stream<String> stream = list.stream();
// 建立一個并行流
Stream<String> parallelStream = list.parallelStream();
2.2 用數組建立流
使用java.util.Arrays.stream(T[] array)方法用數組建立流。
// 建立數組
String[] persons = {"張三", "李四", "王五"};
// 建立一個串行流
Stream<String> stream = Arrays.stream(persons);
// 建立一個并行流
Stream<String> parallelStream = Arrays.stream(persons).parallel();
2.3 Stream靜态方法
使用Stream的靜态方法生成Stream,例如、
of()
、
iterate()
等。
generate()
// 建立數組
String[] persons = {"張三", "李四", "王五"};
// 建立一個串行流
Stream<String> stream = Arrays.stream(persons);
// 建立一個并行流
Stream<String> parallelStream = Arrays.stream(persons).parallel();
三、Stream 使用案例
以下所有案例會基于學生資料,學生類,以及測試資料如下:
package com.nobody;
/**
* 學生類
*
* @Author yus
* @Date 2021/1/21
* @Version 1.0
*/
@Getter
@Setter
@ToString
@AllArgsConstructor
public class Student {
// 主鍵
private String id;
// 姓名
private String name;
// 年齡
private int age;
// 性别
private int sex;
// 成績
private double score;
public Student(String id, String name, int age, int sex, double score) {
this.id = id;
this.name = name;
this.age = age;
this.sex = sex;
this.score = score;
}
List<Student> students = new ArrayList<>(16);
students.add(new Student("1", "張三", 18, "male", 88));
students.add(new Student("2", "李四", 17, "male", 60));
students.add(new Student("3", "王五", 18, "male", 100));
students.add(new Student("4", "趙六", 20, "male", 10));
students.add(new Student("5", "董七", 14, "female", 95));
students.add(new Student("6", "幺八", 21, "male", 55));
students.add(new Student("7", "老九", 20, "female", 66));
students.add(new Student("8", "小明", 18, "male", 100));
students.add(new Student("9", "小紅", 22, "female", 95));
students.add(new Student("10", "小張", 25, "male", 90));
3.1 周遊 forEach
students.stream().forEach(System.out::println);
// 輸出結果
Student{id='1', name='張三', age=18, sex=male, score=88.0}
Student{id='2', name='李四', age=17, sex=male, score=60.0}
Student{id='3', name='王五', age=18, sex=male, score=100.0}
Student{id='4', name='趙六', age=20, sex=male, score=10.0}
Student{id='5', name='董七', age=14, sex=female, score=95.0}
Student{id='6', name='幺八', age=21, sex=male, score=55.0}
Student{id='7', name='老九', age=20, sex=female, score=66.0}
Student{id='8', name='小明', age=18, sex=male, score=100.0}
Student{id='9', name='小紅', age=22, sex=female, score=95.0}
Student{id='10', name='小張', age=25, sex=male, score=90.0}
3.2 過濾 filter
// 過濾出成績100分的學生
List<Student> students1 =
students.stream().filter(student -> student.getScore() == 100).collect(Collectors.toList());
students1.forEach(System.out::println);
// 輸出結果
Student{id='3', name='王五', age=18, sex=male, score=100.0}
Student{id='8', name='小明', age=18, sex=male, score=100.0}
3.3 查找 findFirst,findAny
一般filter和find搭配使用,即從過濾符合條件的資料中,獲得一個資料。
// 串行流,比對第一個
Optional<Student> studentOptional =
students.stream().filter(student -> student.getAge() >= 20).findFirst();
if (studentOptional.isPresent()) {
Student student = studentOptional.get();
System.out.println(student);
}
// 上面輸出語句可簡寫如下
// studentOptional.ifPresent(System.out::println);
// 并行流,比對任意一個,findAny一般用于并行流
Optional<Student> studentOptiona2 =
students.parallelStream().filter(student -> student.getAge() >= 20).findAny();
studentOptiona2.ifPresent(System.out::println);
// 輸出結果
Student{id='4', name='趙六', age=20, sex=male, score=10.0}
Student{id='7', name='老九', age=20, sex=female, score=66.0}
3.4 比對 match
// 是否存在100分的學生
boolean anyMatch = students.stream().anyMatch(student -> student.getScore() == 100);
// 是否全部學生都100分
boolean allMatch = students.stream().allMatch(student -> student.getScore() == 100);
// 是否全部學生都沒有100分
boolean noneMatch = students.stream().noneMatch(student -> student.getScore() == 100);
System.out.println(anyMatch);
System.out.println(allMatch);
System.out.println(noneMatch);
// 輸出結果
true
false
false
3.5 映射 map
映射,顧名思義,将一個對象映射成另外一個對象。即一個Stream流中的所有元素按照一定的映射規則,映射到另一個流中。映射有map和flatMap兩種類型:
- map:接收一個函數作為參數,此函數作用到Stream中每一個元素,形成一個新的元素,所有新的元素組成一個新的流。
- flatMap:接收一個函數作為參數,它将流中的每個元素都轉換成另一個流,然後把所有流再連接配接形成一個最終流。
// 擷取每個學生的姓名
List<String> studentNames =
students.stream().map(Student::getName).collect(Collectors.toList());
System.out.println(studentNames);
// 每個學生的成績加10分
List<Double> studentScores = students.stream().map(student -> student.getScore() + 10)
.collect(Collectors.toList());
System.out.println(studentScores);
// 輸出結果
[張三, 李四, 王五, 趙六, 董七, 幺八, 老九, 小明, 小紅, 小張]
[98.0, 70.0, 110.0, 20.0, 105.0, 65.0, 76.0, 110.0, 105.0, 100.0]
List<String> list = Arrays.asList("a-b-c-d", "g-h-i");
List<String> list1 = list.stream().flatMap(s -> Arrays.stream(s.split("-"))).collect(Collectors.toList());
System.out.println(list1);
// 輸出結果
[a, b, c, d, g, h, i]
3.6 截取流 limit
// limit方法用于擷取指定數量的流。例如下面示範取出學習成績大于70的5個人
List<Student> students2 = students.stream().filter(student -> student.getScore() > 70)
.limit(5).collect(Collectors.toList());
students2.forEach(System.out::println);
// 跳過第一個再取2個
List<Student> students8 = students.stream().skip(1).limit(2).collect(Collectors.toList());
// 擷取5個int随機數,按從小到大排序
Random random = new Random();
random.ints().limit(5).sorted().forEach(System.out::println);
// 輸出結果
Student{id='1', name='張三', age=18, sex=male, score=88.0}
Student{id='3', name='王五', age=18, sex=male, score=100.0}
Student{id='5', name='董七', age=14, sex=female, score=95.0}
Student{id='8', name='小明', age=18, sex=male, score=100.0}
Student{id='9', name='小紅', age=22, sex=female, score=95.0}
-1490202714
145340547
368332155
388399398
1099579920
3.7 排序 sorted
sorted 方法用于對流中的元素進行排序,有兩種排序:
- sorted():自然排序,流中元素需要實作Comparable接口。
- sorted(Comparator<? super T> comparator):需要自定義排序器。
// 按成績升序
List<Student> students3 = students.stream().sorted(Comparator.comparing(Student::getScore))
.collect(Collectors.toList());
System.out.println("按成績升序");
students3.forEach(System.out::println);
// 按成績降序
List<Student> students4 =
students.stream().sorted(Comparator.comparing(Student::getScore).reversed())
.collect(Collectors.toList());
System.out.println("按成績降序");
students4.forEach(System.out::println);
// 按成績升序,再按年齡升序
List<Student> students5 = students.stream()
.sorted(Comparator.comparing(Student::getScore).thenComparing(Student::getAge))
.collect(Collectors.toList());
System.out.println("按成績升序,再按年齡升序");
students5.forEach(System.out::println);
// 按成績升序,再按年齡降序
List<Student> students6 = students.stream().sorted((s1, s2) -> {
if (s1.getScore() != s2.getScore()) {
return (int) (s1.getScore() - s2.getScore());
} else {
return (s2.getAge() - s1.getAge());
}
}).collect(Collectors.toList());
System.out.println("按成績升序,再按年齡降序");
students6.forEach(System.out::println);
3.8 去重 distinct
List<String> list2 = Arrays.asList("a", "b", "a", "c", "f", "e", "f");
List<String> list3 = list2.stream().distinct().collect(Collectors.toList());
Set<String> stringSet = list2.stream().collect(Collectors.toSet()); // list轉set也可以達到去重效果
System.out.println(list3);
System.out.println(stringSet);
// 輸出結果
[a, b, c, f, e]
[a, b, c, e, f]
3.9 統計 summaryStatistics
一些收集器能産生統計結果,例如Collectors提供了一系列用于資料統計的靜态方法,它們主要用于int、double、long等基本類型上。
// 總和統計summaryStatistics
DoubleSummaryStatistics doubleSummaryStatistics =
students.stream().mapToDouble(Student::getScore).summaryStatistics();
System.out.println("平均值:" + doubleSummaryStatistics.getAverage());
System.out.println("總個數:" + doubleSummaryStatistics.getCount());
System.out.println("最大值:" + doubleSummaryStatistics.getMax());
System.out.println("最小值:" + doubleSummaryStatistics.getMin());
System.out.println("總和值:" + doubleSummaryStatistics.getSum());
// 輸出結果
平均值:75.9
總個數:10
最大值:100.0
最小值:10.0
總和值:759.0
// 統計個數
long count = students.stream().count();
// 平均值
Double averageScore =
students.stream().collect(Collectors.averagingDouble(Student::getScore));
// 最大值和最小值
Optional<Double> maxScore = students.stream().map(Student::getScore).max(Double::compare);
Optional<Double> minScore = students.stream().map(Student::getScore).min(Double::compare);
// 求和
double sumScore = students.stream().mapToDouble(Student::getScore).sum();
// 一次性統計所有
DoubleSummaryStatistics doubleSummaryStatistics1 =
students.stream().collect(Collectors.summarizingDouble(Student::getScore));
System.out.println("單個次元計算:");
System.out.println("統計個數:" + count);
System.out.println("平均值:" + averageScore);
maxScore.ifPresent(aDouble -> System.out.println("最大值:" + aDouble));
minScore.ifPresent(aDouble -> System.out.println("最小值:" + aDouble));
System.out.println("求和:" + sumScore);
System.out.println("一次性統計所有:" + doubleSummaryStatistics1);
// 輸出結果
單個次元計算:
統計個數:10
平均值:75.9
最大值:100.0
最小值:10.0
求和:759.0
3.10 歸約 reduce
歸約,把一個流歸約(縮減)成一個值,能實作對集合求和、求乘積和求最值等操作。
List<Integer> integerList = Arrays.asList(6, 7, 1, 10, 11, 7, 13, 20);
// 求和
Optional<Integer> sum1 = integerList.stream().reduce(Integer::sum);
// 求和,基于10的基礎上
Integer sum2 = integerList.stream().reduce(10, Integer::sum);
// 求最大值
Optional<Integer> max1 = integerList.stream().reduce((x, y) -> x > y ? x : y);
// 求最大值,基于與50比較的基礎上
Integer max2 = integerList.stream().reduce(50, Integer::max);
Optional<Integer> min = integerList.stream().reduce(Integer::min);
// 求乘積
Optional<Integer> product = integerList.stream().reduce((x, y) -> x * y);
System.out.println("原始集合:" + integerList);
System.out.println("集合求和:" + sum1.get() + "," + sum2);
System.out.println("集合求最大值:" + max1.get() + "," + max2);
System.out.println("集合求最小值:" + min.get());
System.out.println("集合求積:" + product.get());
// 輸出結果
原始集合:[6, 7, 1, 10, 11, 7, 13, 20]
集合求和:75,85
集合求最大值:20,50
集合求最小值:1
集合求積:8408400
3.11 歸集 toList,toSet,toMap
Java中的Stream不會存儲元素,而是按需計算按照特定的規則對資料進行計算,一般會輸出結果。是以流中的資料完成處理後,需要将流中的資料重新歸集到新的集合裡。比較常用的是、
toList
和
toSet
,以及複雜的toCollection、toConcurrentMap等。
toMap
// 擷取學生名字,形成新的list集合
List<String> studentNames1 =
students.stream().map(Student::getName).collect(Collectors.toList());
// 擷取年齡大于等于15的年齡set集合
Set<Integer> ageSet = students.stream().filter(student -> student.getAge() >= 15)
.map(Student::getAge).collect(Collectors.toSet());
// 建立學生ID和學生實體的map
Map<String, Student> studentMap =
students.stream().collect(Collectors.toMap(Student::getId, student -> student));
System.out.println(studentNames1);
System.out.println(ageSet);
studentMap.forEach((key, value) -> System.out.println(key + ":" + value));
// 輸出結果
[張三, 李四, 王五, 趙六, 董七, 幺八, 老九, 小明, 小紅, 小張]
[17, 18, 20, 21, 22, 25]
1:Student{id='1', name='張三', age=18, sex=male, score=88.0}
2:Student{id='2', name='李四', age=17, sex=male, score=60.0}
3:Student{id='3', name='王五', age=18, sex=male, score=100.0}
4:Student{id='4', name='趙六', age=20, sex=male, score=10.0}
5:Student{id='5', name='董七', age=14, sex=female, score=95.0}
6:Student{id='6', name='幺八', age=21, sex=male, score=55.0}
7:Student{id='7', name='老九', age=20, sex=female, score=66.0}
8:Student{id='8', name='小明', age=18, sex=male, score=100.0}
9:Student{id='9', name='小紅', age=22, sex=female, score=95.0}
10:Student{id='10', name='小張', age=25, sex=male, score=90.0}
3.12 分組 partitioningBy,groupingBy
partitioningBy(分區):stream中的元素按條件被分為兩個Map。
groupingBy(分組):stream中的元素按條件被分為多個Map。
// 按條件學生成績是否大于等于60,劃分為2個組
Map<Boolean, List<Student>> studentScorePart = students.stream()
.collect(Collectors.partitioningBy(student -> student.getScore() >= 60));
// 按性别分組
Map<String, List<Student>> studentSexMap =
students.stream().collect(Collectors.groupingBy(Student::getSex));
// 按年齡分組
Map<Integer, List<Student>> studentAgeMap =
students.stream().collect(Collectors.groupingBy(Student::getAge));
// 先按性别分組,再按年齡分組
Map<String, Map<Integer, List<Student>>> collect = students.stream().collect(
Collectors.groupingBy(Student::getSex, Collectors.groupingBy(Student::getAge)));
System.out.println("按條件學生成績是否大于等于60,劃分為2個組:");
studentScorePart.forEach((aBoolean, students7) -> {
System.out.println("成績大于等于60?:" + aBoolean);
students7.forEach(System.out::println);
});
System.out.println("按性别分組:");
studentSexMap.forEach((sex, students7) -> {
System.out.println("性别?:" + sex);
students7.forEach(System.out::println);
});
System.out.println("按年齡分組:");
studentAgeMap.forEach((age, students7) -> {
System.out.println("年齡:" + age);
students7.forEach(System.out::println);
});
System.out.println("先按性别分組,再按年齡分組:");
collect.forEach((sex, integerListMap) -> {
System.out.println("性别:" + sex);
integerListMap.forEach((age, students7) -> {
System.out.println("年齡:" + age);
students7.forEach(System.out::println);
});
});
//輸出結果
按條件學生成績是否大于等于60,劃分為2個組:
成績大于等于60?:false
Student{id='4', name='趙六', age=20, sex=male, score=10.0}
Student{id='6', name='幺八', age=21, sex=male, score=55.0}
成績大于等于60?:true
Student{id='1', name='張三', age=18, sex=male, score=88.0}
Student{id='2', name='李四', age=17, sex=male, score=60.0}
Student{id='3', name='王五', age=18, sex=male, score=100.0}
Student{id='5', name='董七', age=14, sex=female, score=95.0}
Student{id='7', name='老九', age=20, sex=female, score=66.0}
Student{id='8', name='小明', age=18, sex=male, score=100.0}
Student{id='9', name='小紅', age=22, sex=female, score=95.0}
Student{id='10', name='小張', age=25, sex=male, score=90.0}
按性别分組:
性别?:female
Student{id='5', name='董七', age=14, sex=female, score=95.0}
Student{id='7', name='老九', age=20, sex=female, score=66.0}
Student{id='9', name='小紅', age=22, sex=female, score=95.0}
性别?:male
Student{id='1', name='張三', age=18, sex=male, score=88.0}
Student{id='2', name='李四', age=17, sex=male, score=60.0}
Student{id='3', name='王五', age=18, sex=male, score=100.0}
Student{id='4', name='趙六', age=20, sex=male, score=10.0}
Student{id='6', name='幺八', age=21, sex=male, score=55.0}
Student{id='8', name='小明', age=18, sex=male, score=100.0}
Student{id='10', name='小張', age=25, sex=male, score=90.0}
按年齡分組:
年齡:17
Student{id='2', name='李四', age=17, sex=male, score=60.0}
年齡:18
Student{id='1', name='張三', age=18, sex=male, score=88.0}
Student{id='3', name='王五', age=18, sex=male, score=100.0}
Student{id='8', name='小明', age=18, sex=male, score=100.0}
年齡:20
Student{id='4', name='趙六', age=20, sex=male, score=10.0}
Student{id='7', name='老九', age=20, sex=female, score=66.0}
年齡:21
Student{id='6', name='幺八', age=21, sex=male, score=55.0}
年齡:22
Student{id='9', name='小紅', age=22, sex=female, score=95.0}
年齡:25
Student{id='10', name='小張', age=25, sex=male, score=90.0}
年齡:14
Student{id='5', name='董七', age=14, sex=female, score=95.0}
先按性别分組,再按年齡分組:
性别:female
年齡:20
Student{id='7', name='老九', age=20, sex=female, score=66.0}
年齡:22
Student{id='9', name='小紅', age=22, sex=female, score=95.0}
年齡:14
Student{id='5', name='董七', age=14, sex=female, score=95.0}
性别:male
年齡:17
Student{id='2', name='李四', age=17, sex=male, score=60.0}
年齡:18
Student{id='1', name='張三', age=18, sex=male, score=88.0}
Student{id='3', name='王五', age=18, sex=male, score=100.0}
Student{id='8', name='小明', age=18, sex=male, score=100.0}
年齡:20
Student{id='4', name='趙六', age=20, sex=male, score=10.0}
年齡:21
Student{id='6', name='幺八', age=21, sex=male, score=55.0}
年齡:25
Student{id='10', name='小張', age=25, sex=male, score=90.0}
3.13 合并 joining
将stream中的元素用指定的連接配接符(沒有的話,則直接連接配接)連接配接成一個字元串。
String joinName = students.stream().map(Student::getName).collect(Collectors.joining(", "));
System.out.println(joinName);
// 輸出結果
張三, 李四, 王五, 趙六, 董七, 幺八, 老九, 小明, 小紅, 小張