Java 8 Stream 知识点详细讲解
什么是Stream
Stream 是 java 8 的新特性,Stream 是对集合功能的增强,它不是集合元素也不是数据结构,更不能用于保存数据,它是有关于算法和计算的。
Stream 将要处理的元素集合看作一种流,在流的过程中,借助 Stream API 对流中的元素进行操作,如查询、筛选、删除、过滤、统计、映射等。
也就是说 Stream 本身不负责存储数据,存储数据是用集合,数组等数据结构,它只负责对数据进行处理、加工。
Stream 的操作流程
- 创建 Stream :Stream 数据的来源
- 加工处理:可以是查询、筛选、删除、过滤等操作的一种或多种结合
- 终结操作:收集结果,一旦终结就不能再加工了,如果要加工需要重新创建Stream。
Stream 的创建
- 使用 Arrays.stream() 传入参数创建 Stream
Stream<String> stream1 = Arrays.stream(new String[]{"A", "B", "C", "D"});
stream1.forEach(System.out::println);
执行结果:
A
B
C
D
- 使用 Collection.stream() 方法返回 Stream
List<String> list = Arrays.asList("A", "B", "C", "D");
Stream<String> stream2 = list.stream();
Stream<String> parallelStream = list.parallelStream();
stream2.forEach(System.out::println);
执行结果:
A
B
C
D
注:stream 和 parallelStream 的区别:
- stream 是顺序流,由主线程按顺序对流执行操作
- parallelStream 里面的执行是异步的,并且使用的线程池是ForkJoinPool.common,可以通过设置Djava.util.concurrent.ForkJoinPool.common.parallelism = N来调整线程池的大小
- 使用 Stream 类下的静态方法 of()、iterate()、generate() 创建 Stream
// of()
Stream<String> stream3 = Stream.of("A", "B", "C", "D");
stream3.forEach(System.out::println);
// iterate()
Stream<String> stream4 = Stream.iterate("A", temp -> temp + "B").limit(4);
stream4.forEach(System.out::println);
// generate()
Stream<UUID> stream5 = Stream.generate(UUID::randomUUID).limit(4);
stream5.forEach(System.out::println);
执行结果:
A
B
C
D
------------------------------------
A
AB
ABB
ABBB
------------------------------------
df4c1baa-765c-4aea-9052-f1033fa821bd
93630562-3f62-45f6-9fca-828ffeb76272
51bf8b72-785b-4c07-8e7a-b200842cb588
d57b97c0-f6ec-4a76-bc82-6c6e8c8654f1
Stream 的详细使用
在详细使用 Stream 之前需要先知道什么是 Optional 类。Java8 新增了非常多的特性,而 Optional 类就是其中一个新增的类。
- Optional 类是一个可以为 null 的容器对象。
- 如果值存在则 isPresent() 方法会返回 true,调用 get() 方法会返回该对象。
- Optional 容器可以保存类型 T 的值,或者仅仅保存 null。
- Optional 提供很多有用的方法,这样我们就不用显式进行空值检测。
- Optional 类的引入很好的解决空指针异常。
操作需要的 Student 类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private String name;
private Integer age;
private String hobby;
private Integer score;
public static List<Student> getStudents() {
List<Student> list = new ArrayList<>();
list.add(new Student("张三", 24, "basketball", 95));
list.add(new Student("李四", 15, "basketball", 78));
list.add(new Student("王五", 30, "basketball", 80));
list.add(new Student("赵六", 18, "basketball", 85));
list.add(new Student("李刚", 22, "basketball", 68));
list.add(new Student("小明", 16, "basketball", 100));
return list;
}
}
注:还没有用过 Lombok 的同学一定要了解一下哦。
find 和 match(查找和匹配)
// find 找到匹配规则的对象
// match 是否存在匹配规则对象
public class TestStreamFind {
public static void main(String[] args) {
List<Student> students = Student.getStudents();
// findFirst() 找到符合标准的第一个
Optional<Student> first = students.stream().filter(stu -> stu.getAge() > 22).findFirst();
System.out.println("findFist() 找到第一个匹配对象:" + first.get());
// findAny() 找到符合标准的随机一个,一般用于并行流中
Optional<Student> any = students.stream().filter(stu -> stu.getScore() > 90).findAny();
System.out.println("-------------------------------------");
System.out.println("findAny() 找到随机一个匹配对象:" + any.get());
// aynMatch() 是否存在匹配对象
boolean result = students.stream().anyMatch(stu -> stu.getScore() < 50);
System.out.println("-------------------------------------");
System.out.println("anyMatch() 返回是否存在匹配对象:" + result);
}
}
执行结果
findFist() 找到第一个匹配对象:Student(name=张三, age=24, hobby=basketball, score=88)
-------------------------------------
findAny() 找到随机一个匹配对象:Student(name=赵六, age=18, hobby=basketball, score=95)
-------------------------------------
anyMatch() 返回是否存在匹配对象:false
filter 和 forEach(筛选和遍历)
// filter 筛选符合规则的对象
// forEach 遍历流中的对象
// collect 收集流中的内容
public class TestStreamFilter {
public static void main(String[] args) {
List<Student> list = Student.getStudents();
list.stream().filter(stu -> stu.getAge() > 20).forEach(System.out::println);
// 将 stream() 收集成集合
List<Student> newList = list.stream().filter(stu -> stu.getAge() > 20).collect(Collectors.toList());
System.out.println("--------------------------------");
System.out.println(newList);
}
}
执行结果
Student(name=张三, age=24, hobby=basketball, score=88)
Student(name=王五, age=30, hobby=basketball, score=80)
Student(name=李刚, age=22, hobby=basketball, score=68)
--------------------------------
[Student(name=张三, age=24, hobby=basketball, score=88), Student(name=王五, age=30, hobby=basketball, score=80), Student(name=李刚, age=22, hobby=basketball, score=68)]
max、min 和 count(最大值,最小值和计数)
// max 找到符合规则的最大值
// min 找到符合规则的最大值
// count 找到符合规则的个数
public class TestStreamMinMaxCount {
public static void main(String[] args) {
List<Student> list = Student.getStudents();
Optional<Student> max = list.stream().max(Comparator.comparing(Student::getAge));
System.out.println("max() 找到的年龄最大的对象:" + max.get());
Optional<Student> min = list.stream().min(Comparator.comparing(Student::getScore));
System.out.println("min() 找到的分数最小的对象:" + max.get());
long count = list.stream().filter(stu -> stu.getScore() > 90).count();
System.out.println("count() 找到分数在90分以上的对象的个数:" + count);
}
}
执行结果
max() 找到的年龄最大的对象:Student(name=王五, age=30, hobby=basketball, score=80)
min() 找到的分数最小的对象:Student(name=王五, age=30, hobby=basketball, score=80)
count() 找到分数在90分以上的对象的个数:2
map 和 flatMap(映射)
public class TestStreamMap {
public static void main(String[] args) {
// 获取年龄大于 23 的同学
List<Student> list = Student.getStudents().stream().filter(stu -> stu.getAge() > 23).collect(Collectors.toList());
// 遍历该 list
list.forEach(System.out::println);
System.out.println("----------------------所有同学年龄加1-------------------------");
// 让该 list 中的所有同学年龄加 1
// collect 将 stream 转成集合
list.stream().map(stu -> {
stu.setAge(stu.getAge() + 1);
return stu;
}).collect(Collectors.toList()).forEach(System.out::println);
// flatMap 将两个 list 合一
System.out.println("----------------------flatMap将两个list合一-------------------------");
List<Student> list2 = Student.getStudents().stream().filter(stu -> stu.getAge() > 23).collect(Collectors.toList());
List<List<Student>> nowList = Arrays.asList(list, list2);
nowList.stream().flatMap(stus -> {
Stream<Student> stream = stus.stream();
return stream;
}).forEach(System.out::println);
}
}
执行结果
Student(name=张三, age=24, hobby=basketball, score=88)
Student(name=王五, age=30, hobby=basketball, score=80)
----------------------所有同学年龄加1-------------------------
Student(name=张三, age=25, hobby=basketball, score=88)
Student(name=王五, age=31, hobby=basketball, score=80)
----------------------flatMap将两个list合一-------------------------
Student(name=张三, age=25, hobby=basketball, score=88)
Student(name=王五, age=31, hobby=basketball, score=80)
Student(name=张三, age=24, hobby=basketball, score=88)
Student(name=王五, age=30, hobby=basketball, score=80)
reduce(累加器)
// reduce 其实它不是累加,应该说是累计计算,先把前两个根据规则算出结果,可以是累加,累乘,比大小等等
// 再和第 3 个算出结果,依此类推,直到所有元素运算完毕。
// 如果传入第 1 个参数,则先将该参数与第一个元素结合运算
public class TestStreamReduce {
public static void main(String[] args) {
// 获取累加的 3 种方式
List<Integer> list = Arrays.asList(1, 3, 5, 7, 9);
// 方式 1
Optional<Integer> sum1 = list.stream().reduce((x, y) -> x + y);
// 方式 2
Optional<Integer> sum2 = list.stream().reduce(Integer::sum);
// 方式 3
Integer sum3 = list.stream().reduce(0, Integer::sum);
System.out.println("reduce() 的求和结果为:" + sum3);
// 分数求和
List<Student> students = Student.getStudents();
Optional<Integer> result1 = students.stream().map(Student::getScore).reduce(Integer::sum);
Integer result2 = students.stream().reduce(0, (sum, stu) -> sum += stu.getScore(), (param1, param2) -> param1 + param2);
Integer result3 = students.stream().reduce(0, (sum, stu) -> sum += stu.getScore(), Integer::sum);
System.out.println("reduce() 的分数求和结果为:" + result3);
// 分数最大值
students.stream().reduce(0, (max, stu) -> max > stu.getScore() ? max : stu.getScore(), (param1, param2) -> param1 > param2 ? param1 : param2);
Integer maxScore = students.stream().reduce(0, (max, stu) -> max > stu.getScore() ? max : stu.getScore(), Integer::max);
System.out.println("reduce() 的求分数最大值结果为:" + maxScore);
}
}
执行结果
reduce() 的求和结果为:25
reduce() 的分数求和结果为:509
reduce() 的求分数最大值结果为:100
collect(收集)
toList、toSet、toMap(转换)
public class TestStreamCollect {
public static void main(String[] args) {
// toList() 将分数大于 90 的学生的信息从流收集到 List 中
Student.getStudents().stream().filter(stu -> stu.getScore() > 90).collect(Collectors.toList()).forEach(System.out::println);
// toSet() 将 1,2,3,4,1 收集到 Set 中
System.out.println("----------------------------------------------");
Arrays.asList(1, 2, 3, 4, 1).stream().collect(Collectors.toSet()).forEach(System.out::println);
// toMap() 将所有同学的 List 集合转成流,再收集成 k,v = name,Student 的 map
System.out.println("----------------------------------------------");
Student.getStudents().stream().collect(Collectors.toMap(Student::getName, stu -> stu)).entrySet().forEach(System.out::println);
}
}
执行结果
Student(name=赵六, age=18, hobby=basketball, score=95)
Student(name=小明, age=16, hobby=basketball, score=100)
----------------------------------------------
1
2
3
4
----------------------------------------------
李刚=Student(name=李刚, age=22, hobby=basketball, score=68)
李四=Student(name=李四, age=15, hobby=basketball, score=78)
张三=Student(name=张三, age=24, hobby=basketball, score=88)
小明=Student(name=小明, age=16, hobby=basketball, score=100)
王五=Student(name=王五, age=30, hobby=basketball, score=80)
赵六=Student(name=赵六, age=18, hobby=basketball, score=95)
joining(拼接)
// join 拼接字符串
public class TestStreamJoining {
public static void main(String[] args) {
// 将所有同学的名字用 , 隔开
System.out.println(Student.getStudents().stream().map(Student::getName).collect(Collectors.joining(",")));
// 将所有同学的名字用 , 隔开,并用 [ 当前缀,用 ] 当后缀
System.out.println(Student.getStudents().stream().map(Student::getName).collect(Collectors.joining(",","[","]")));
}
}
执行结果
张三,李四,王五,赵六,李刚,小明
[张三,李四,王五,赵六,李刚,小明]
groupingBy 和 partitioningBy(分组)
// groupingBy 根据条件进行分组
public class TestStreamGroupingBy {
public static void main(String[] args) {
// 通过 hobby 对 List 进行分组
Student.getStudents().stream().collect(Collectors.groupingBy(Student::getHobby)).entrySet().forEach(System.out::println);
// 通过 age 是否大于 20 对 List 进行分组
System.out.println("-----------------------------------------");
Student.getStudents().stream().collect(Collectors.partitioningBy(stu -> stu.getAge() > 20)).entrySet().forEach(System.out::println);
}
}
执行结果
soccer=[Student(name=王五, age=30, hobby=soccer, score=80), Student(name=赵六, age=18, hobby=soccer, score=95)]
basketball=[Student(name=张三, age=24, hobby=basketball, score=88), Student(name=李刚, age=22, hobby=basketball, score=68)]
run=[Student(name=小明, age=16, hobby=run, score=100)]
baseball=[Student(name=李四, age=15, hobby=baseball, score=78)]
-----------------------------------------
false=[Student(name=李四, age=15, hobby=baseball, score=78), Student(name=赵六, age=18, hobby=soccer, score=95), Student(name=小明, age=16, hobby=run, score=100)]
true=[Student(name=张三, age=24, hobby=basketball, score=88), Student(name=王五, age=30, hobby=soccer, score=80), Student(name=李刚, age=22, hobby=basketball, score=68)]
counting 和 averagingLong(统计)
Collectors 类中有一系列常用的统计方法
- 计数:count
- 平均值:averagingInt、averagingLong、averagingDouble
- 最值:maxBy、minBy
- 求和:summingInt、summingLong、summingDouble
- 统计所有:summarizingInt、summarizingLong、summarizingDouble
public class TestStreamCountAvrage {
public static void main(String[] args) {
// 计算学生的总数
Long count = Student.getStudents().stream().collect(Collectors.counting());
System.out.println("counting() 方法计算的总数为:" + count);
// 计算学生平均分数
Double averageScore = Student.getStudents().stream().collect(Collectors.averagingLong(Student::getScore));
System.out.println("averagingLong() 方法计算的平均分为:" + averageScore);
// 计算年龄最大的学生
Optional<Integer> maxAge = Student.getStudents().stream().map(Student::getAge).collect(Collectors.maxBy(Integer::compareTo));
System.out.println("maxBy() 方法计算的最大年龄为:" + maxAge.get());
// 计算所有同学的分数和
Integer sumScore = Student.getStudents().stream().collect(Collectors.summingInt(Student::getScore));
System.out.println("summingInt() 方法计算所有学生分数和为:" + sumScore);
// 统计同学关于分数的所有信息
DoubleSummaryStatistics collect = Student.getStudents().stream().collect(Collectors.summarizingDouble(Student::getScore));
System.out.println("summarizingDouble() 统计关于分数的信息:" + collect);
}
}
执行结果
counting() 方法计算的总数为:6
averagingLong() 方法计算的平均分为:84.83333333333333
maxBy() 方法计算的最大年龄为:30
summingInt() 方法计算所有学生分数和为:509
summarizingDouble() 统计关于分数的信息:DoubleSummaryStatistics{count=6, sum=509.000000, min=68.000000, average=84.833333, max=100.000000}
reducing(累加)
Collectors 类提供的 reducing 方法,相比于 stream 本身的 reduce,增加了对自定义条件的支持。
// reducing
public class TestStreamReducing {
public static void main(String[] args) {
// 分数错误每人多了 10 分,求所有同学正确的分数和
Integer rightScore = Student.getStudents().stream().collect(Collectors.reducing(0, Student::getScore,
(param1, param2) -> param1 + param2 - 10));
System.out.println("reducing() 方法计算所有同学分数减10分的总分数:" + rightScore);
}
}
执行结果
reducing() 方法计算所有同学分数减10分的总分数:449
sorted(排序)
public class TestStreamSort {
public static void main(String[] args) {
System.out.println("按分数升序排序:");
Student.getStudents().stream().sorted(Comparator.comparing(Student::getScore)).forEach(System.out::println);
System.out.println("-----------------------------------------------------------");
System.out.println("按分数降序排序:");
Student.getStudents().stream().sorted(Comparator.comparing(Student::getScore).reversed()).forEach(System.out::println);
System.out.println("-----------------------------------------------------------");
System.out.println("先按分数再按年龄年龄排序:");
Student.getStudents().stream().sorted(Comparator.comparing(Student::getScore).thenComparing(Student::getAge)).forEach(System.out::println);
System.out.println("-----------------------------------------------------------");
System.out.println("先按分数再按年龄自定义降序排序:");
Student.getStudents().stream().sorted((stu1, stu2) -> {
if (stu1.getScore() == stu2.getScore())
return stu2.getAge() - stu1.getAge();
return stu1.getScore() - stu2.getScore();
}).forEach(System.out::println);
}
}
执行结果
按分数升序排序:
Student(name=李四, age=15, hobby=baseball, score=78)
Student(name=李刚, age=22, hobby=basketball, score=78)
Student(name=王五, age=30, hobby=soccer, score=80)
Student(name=赵六, age=18, hobby=soccer, score=80)
Student(name=张三, age=24, hobby=basketball, score=88)
Student(name=小明, age=16, hobby=run, score=100)
-----------------------------------------------------------
按分数降序排序:
Student(name=小明, age=16, hobby=run, score=100)
Student(name=张三, age=24, hobby=basketball, score=88)
Student(name=王五, age=30, hobby=soccer, score=80)
Student(name=赵六, age=18, hobby=soccer, score=80)
Student(name=李四, age=15, hobby=baseball, score=78)
Student(name=李刚, age=22, hobby=basketball, score=78)
-----------------------------------------------------------
先按分数再按年龄年龄排序:
Student(name=李四, age=15, hobby=baseball, score=78)
Student(name=李刚, age=22, hobby=basketball, score=78)
Student(name=赵六, age=18, hobby=soccer, score=80)
Student(name=王五, age=30, hobby=soccer, score=80)
Student(name=张三, age=24, hobby=basketball, score=88)
Student(name=小明, age=16, hobby=run, score=100)
-----------------------------------------------------------
先按分数再按年龄自定义降序排序:
Student(name=李刚, age=22, hobby=basketball, score=78)
Student(name=李四, age=15, hobby=baseball, score=78)
Student(name=王五, age=30, hobby=soccer, score=80)
Student(name=赵六, age=18, hobby=soccer, score=80)
Student(name=张三, age=24, hobby=basketball, score=88)
Student(name=小明, age=16, hobby=run, score=100)
concat、limit、skip(连接,限长,跳跃)
public class TestStreamConcatLimitSkipDistinct {
public static void main(String[] args) {
Stream<Student> stream1 = Student.getStudents().stream().filter(stu -> stu.getAge() > 23);
Stream<Student> stream2 = Student.getStudents().stream().filter(stu -> stu.getScore() > 90);
System.out.println("--------------使用 concat 连接流,使用 distinct 去重----------------");
Stream.concat(stream1, stream2).distinct().forEach(System.out::println);
System.out.println("---------------------使用 limit 限定只要前几个----------------------");
Student.getStudents().stream().limit(3).forEach(System.out::println);
System.out.println("---------------------使用 skip 跳过指定个数-----------------------");
Student.getStudents().stream().skip(3).forEach(System.out::println);
}
}
执行结果
--------------使用 concat 连接流,使用 distinct 去重----------------
Student(name=张三, age=24, hobby=basketball, score=88)
Student(name=王五, age=30, hobby=soccer, score=80)
Student(name=小明, age=25, hobby=run, score=100)
---------------------使用 limit 限定只要前几个----------------------
Student(name=张三, age=24, hobby=basketball, score=88)
Student(name=李四, age=15, hobby=baseball, score=78)
Student(name=王五, age=30, hobby=soccer, score=80)
---------------------使用 skip 跳过指定个数-----------------------
Student(name=赵六, age=18, hobby=soccer, score=80)
Student(name=李刚, age=22, hobby=basketball, score=78)
Student(name=小明, age=25, hobby=run, score=100)