前言
本文主要介绍Stream流操作的常用使用方法,也算是在日常工作中,对流和函数式编程的用法总结。
- 关于函数编程的原理可以参考:Java 8函数式编程#Lambda表达式#方法引用
- 关于Collectors.groupingBy可以参考:Stream Collectors.groupingBy的四种用法 解决分组统计(计数、求和、平均数等)、范围统计、分组合并、分组结果自定义映射等问题
目录
前置数据
初始化队列数据:
List<Student> students = Stream.of(
Student.builder().name("小张").age(16).clazz("高一1班").course("历史").score(88).build(),
Student.builder().name("小李").age(16).clazz("高一3班").course("数学").score(12).build(),
Student.builder().name("小王").age(17).clazz("高二1班").course("地理").score(44).build(),
Student.builder().name("小红").age(18).clazz("高二1班").course("物理").score(67).build(),
Student.builder().name("李华").age(15).clazz("高二2班").course("数学").score(99).build(),
Student.builder().name("小潘").age(19).clazz("高三4班").course("英语").score(100).build(),
Student.builder().name("小聂").age(20).clazz("高三4班").course("物理").score(32).build()
).collect(Collectors.toList());
用法实例
检视流中的元素
如果我们需要在流式操作中查看某个环节的元素情况,或者修改流中的某个数据项,就可以通过peek检视元素。
语法:
Stream peek(Consumer<? super T> action)
用例:
// 检视元素
List<String> peek = students.stream()
.filter(s -> s.getScore() > 60)
.peek(System.out::println)
// peek还可以用来修改元素内容
//.peek(s -> s.setScore(60))
.map(Student::getName)
.peek(System.out::println)
.collect(Collectors.toList());
System.out.println(peek);
结果:
Student(name=小张, age=16, clazz=高一1班, score=88, course=历史)
小张
Student(name=小红, age=18, clazz=高二1班, score=67, course=物理)
小红
Student(name=李华, age=15, clazz=高二2班, score=99, course=数学)
李华
Student(name=小潘, age=19, clazz=高三4班, score=100, course=英语)
小潘
[小张, 小红, 李华, 小潘]
流转集合,合并元素
语法:
<R, A> R collect(Collector<? super T, A, R> collector)
<R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner)
- supplier:无参构造器,提供一个无参初始化容器的方法
- accumulator:累加器,提供容器增加元素的方法
- combiner:合并函数,提供一个容器合并的方法(因为流内部会使用并行的方式,多个线程会创建多个容器添加元素,以提高执行效率)
用例:
// 流转集合
List<Student> studentList = students.stream().collect(Collectors.toList());
// 使用该方法,可以把流转map或任意集合对象
HashMap<String, Student> map = students.stream().collect(HashMap::new, (hashMap, student) -> hashMap.put(student.getName(), student), HashMap::putAll);
System.out.println(studentList);
System.out.println(map);
结果:
[Student(name=小张, age=16, clazz=高一1班, score=88, course=历史), Student(name=小李, age=16, clazz=高一3班, score=12, course=数学), Student(name=小王, age=17, clazz=高二1班, score=44, course=地理), Student(name=小红, age=18, clazz=高二1班, score=67, course=物理), Student(name=李华, age=15, clazz=高二2班, score=99, course=数学), Student(name=小潘, age=19, clazz=高三4班, score=100, course=英语), Student(name=小聂, age=20, clazz=高三4班, score=32, course=物理)]
{小潘=Student(name=小潘, age=19, clazz=高三4班, score=100, course=英语), 小李=Student(name=小李, age=16, clazz=高一3班, score=12, course=数学), 小王=Student(name=小王, age=17, clazz=高二1班, score=44, course=地理), 小红=Student(name=小红, age=18, clazz=高二1班, score=67, course=物理), 小聂=Student(name=小聂, age=20, clazz=高三4班, score=32, course=物理), 小张=Student(name=小张, age=16, clazz=高一1班, score=88, course=历史), 李华=Student(name=李华, age=15, clazz=高二2班, score=99, course=数学)}
映射
语法:
Stream map(Function<? super T, ? extends R> mapper)
提供一个方法,该方法的返回值将作为流的操作元素
IntStream mapToInt(ToIntFunction<? super T> mapper)
mapToInt和mapToDouble是用于操作整型和浮点型的元素并提供了计数求和求平均值等算术操作,如果要操作流,需要装箱boxed()
DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper)
用例:
// 获取学生姓名的新列表
List<String> names = students.stream().map(Student::getName).collect(Collectors.toList());
List<Integer> ages = students.stream().mapToInt(Student::getAge).boxed().collect(Collectors.toList());
System.out.println(names);
System.out.println(ages);
结果:
[小张, 小李, 小王, 小红, 李华, 小潘, 小聂]
[16, 16, 17, 18, 15, 19, 20]
合并元素
语法:
<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner)
identity:合并标识值(因子),它将参与累加函数和合并函数的运算(即提供一个默认值,在流为空时返回该值,当流不为空时,该值作为起始值,参与每一次累加或合并计算)
accumulator:累加函数(将流元素和identity进行累加,流元素可以不与identity相同,但是在累加函数中,需要确保返回的值类型与identity相同)
combiner:合并函数(合并多个标识值,与collect方法的combiner参数原理类似,都是用于多线程时的合并策略)
// identity和accumulator操作原理大致如下
// 而合并函数的作用是将多个result相加(多线操作时的合并),相当于多个线程同时执行下面的方法,最后将这些方法执行的结构合并
U result = identity;
for (T element : this stream)
result = accumulator.apply(result, element)
return result;
用例:
// 合并所有姓名
String reduce = students.stream().reduce(new StringBuffer(), (result, student) -> result.append(student.getName()), (r1, r2) -> r1.append(r2.toString())).toString();
// 简单写法,通过map和reduce操作的显式组合,能更简单的表示
Optional<String> reduce1 = students.stream().map(Student::getName).reduce(String::concat);
// 当然如果只是简单的字符串拼接,完全可以直接使用Collectors.joining的连接函数来实现
String reduce2 = students.stream().map(Student::getName).collect(Collectors.joining(","))
System.out.println(reduce);
结果:
小张小李小王小红李华小潘小聂
关于identity的示例:
// 这里标识值为0,累加器将会从如下等式开始累加计算:0 + student.getScore()
Integer reduce2 = students.stream().reduce(0, (integer, student) -> integer + student.getScore(), Integer::sum);
// 这里标识值为10,累加器将会从如下等式开始累加计算:10 + student.getScore()
Integer reduce3 = students.stream().reduce(10, (integer, student) -> integer + student.getScore(), Integer::sum);
System.out.println(reduce2);
System.out.println(reduce3);
结果:
442
452
过滤筛选
语法:
Stream filter(Predicate<? super T> predicate)
用例:
// 只获取高一的数学成绩
List<Student> math = students.stream()
.filter(student -> student.getClazz().contains("高一") && "数学".equals(student.getCourse()))
.collect(Collectors.toList());
System.out.println(math);
结果:
[Student(name=小李, age=16, clazz=高一3班, score=12, course=数学)]
截断(物理分页)
语法:
Stream skip(long n)
Stream limit(long maxSize)
用例:
// 取第二页,每页五条数据
List<Student> page = students.stream().skip(5).limit(5).collect(Collectors.toList());
System.out.println(page);
结果:
[Student(name=小潘, age=19, clazz=高三4班, score=100, course=英语), Student(name=小聂, age=20, clazz=高三4班, score=32, course=物理)]
排序
语法:
Stream sorted()
按照自然规则排序
Stream sorted(Comparator<? super T> comparator)
根据比较器进行排序,可以通过实现Comparator#compare方法,来创建自定义的比较器
用例:
// 按成绩排序,成绩相同时根据年龄排倒序
List<Student> sorted = students.stream()
.sorted(Comparator.comparing(Student::getScore, Comparator.reverseOrder()).thenComparing(Student::getAge, Comparator.reverseOrder()))
.collect(Collectors.toList());
// 普通写法
List<Student> sorted2 = students.stream()
.sorted((c1, c2) -> {
if (c1.getScore() > c2.getScore()) {
return -1;
} else if (c1.getScore().equals(c2.getScore())) {
if (c1.getAge()>c2.getAge()) {
return -1;
} else if (c1.getAge() < c2.getAge()){
return 1;
}
return 0;
}
return 1;
})
.collect(Collectors.toList());
System.out.println(sorted);
System.out.println(sorted2);
结果:
[Student(name=小潘, age=19, clazz=高三4班, score=100, course=英语), Student(name=李华, age=15, clazz=高二2班, score=99, course=数学), Student(name=小张, age=16, clazz=高一1班, score=88, course=历史), Student(name=小红, age=18, clazz=高二1班, score=67, course=物理), Student(name=小王, age=17, clazz=高二1班, score=44, course=地理), Student(name=小聂, age=20, clazz=高三4班, score=32, course=物理), Student(name=小李, age=16, clazz=高一3班, score=12, course=数学)]
[Student(name=小潘, age=19, clazz=高三4班, score=100, course=英语), Student(name=李华, age=15, clazz=高二2班, score=99, course=数学), Student(name=小张, age=16, clazz=高一1班, score=88, course=历史), Student(name=小红, age=18, clazz=高二1班, score=67, course=物理), Student(name=小王, age=17, clazz=高二1班, score=44, course=地理), Student(name=小聂, age=20, clazz=高三4班, score=32, course=物理), Student(name=小李, age=16, clazz=高一3班, score=12, course=数学)]
最大值最小值
语法:
Optional max(Comparator<? super T> comparator)
Optional min(Comparator<? super T> comparator)
用例:
// 分数的最大值 最小值
Student max = students.stream().max(Comparator.comparing(Student::getScore)).orElse(null);
Student min = students.stream().min(Comparator.comparing(Student::getScore)).orElse(null);
// 也可以通过如下写法获取最大值,这种写法只能获取到值,没法关联用户,跟上面的写法各有用途
int max2 = students.stream().mapToInt(Student::getScore).max().orElse(0);
int min2 = students.stream().mapToInt(Student::getScore).min().orElse(0);
System.out.println(max);
System.out.println(min);
结果:
Student(name=小潘, age=19, clazz=高三4班, score=100, course=英语)
Student(name=小李, age=16, clazz=高一3班, score=12, course=数学)
计数求和、平均值
语法:
long count()
Optional reduce(BinaryOperator accumulator)
通过元素累加实现求和
用例:
// 计数求及格的学生人数
long count = students.stream().filter(student -> student.getScore() > 60).count();
// 求分数总和
Integer sum = students.stream().map(Student::getScore).reduce(Integer::sum).orElse(-1);
Integer sum2 = students.stream().mapToInt(Student::getScore).sum();
// 分数的平均值
double average = students.stream().mapToDouble(Student::getScore).average().orElse(0D);
System.out.println(count + "-" + sum + "-" + average);
结果:
4-442-63.142857142857146
分组
语法:
Collector<T, ?, M> groupingBy(Function<? super T, ? extends K> classifier, Supplier<M> mapFactory, Collector<? super T, A, D> downstream)
- classifier:提供一个方法,该方法的返回值是键值对的键
- mapFactory:提供一个容器初始化方法,用于创建新的Map容器(使用该容器存放值对),与之前的方法类型,这里同样可以使用并发加快流的处理速度。
- downstream:同一分组的合并方法,将同一个类型合并为指定类型,该方法返回的是键值对的值
更多分组示例查看:Stream Collectors.groupingBy的四种用法 解决分组统计(计数、求和、平均数等)、范围统计、分组合并、分组结果自定义映射等问题
用例:
// 将不同课程的学生进行分类
HashMap<String, List<Student>> groupByCourse = (HashMap<String, List<Student>>)students.stream()
.collect(Collectors.groupingBy(Student::getCourse));
// 上面的方法中,最终返回默认是HashMap,键值对中的值默认是ArrayList,可以通过下面的方法自定义返回结果、值的类型
HashMap<String, List<Student>> groupByCourse1 = students.stream()
.collect(Collectors.groupingBy(Student::getCourse, HashMap::new, Collectors.toList()));
// 增加映射功能,将值设置为名字
HashMap<String, List<String>> groupMapping = students.stream()
.collect(Collectors.groupingBy(Student::getCourse, HashMap::new, Collectors.mapping(Student::getName, Collectors.toList())));
// 增加合并函数,计算每科总分
HashMap<String, Integer> groupCalcSum = students.stream()
.collect(Collectors.groupingBy(Student::getCourse, HashMap::new, Collectors.reducing(0, Student::getScore, Integer::sum)));
// 增加平均值计算
HashMap<String, Double> groupCalcAverage = students.stream()
.collect(Collectors.groupingBy(Student::getCourse, HashMap::new, Collectors.averagingDouble(Student::getScore)));
System.out.println(groupByCourse);
System.out.println(groupMapping);
System.out.println(groupCalcSum);
System.out.println(groupCalcAverage);
结果:
{物理=[Student(name=小红, age=18, clazz=高二1班, score=67, course=物理), Student(name=小聂, age=20, clazz=高三4班, score=32, course=物理)], 历史=[Student(name=小张, age=16, clazz=高一1班, score=88, course=历史)], 数学=[Student(name=小李, age=16, clazz=高一3班, score=12, course=数学), Student(name=李华, age=15, clazz=高二2班, score=99, course=数学)], 英语=[Student(name=小潘, age=19, clazz=高三4班, score=100, course=英语)], 地理=[Student(name=小王, age=17, clazz=高二1班, score=44, course=地理)]}
{物理=[小红, 小聂], 历史=[小张], 数学=[小李, 李华], 英语=[小潘], 地理=[小王]}
{物理=99, 历史=88, 数学=111, 英语=100, 地理=44}
{物理=49.5, 历史=88.0, 数学=55.5, 英语=100.0, 地理=44.0}
去重
语法:
Stream distinct()
用例:
// 去重 统计所有科目
List<String> courses = students.stream().map(Student::getCourse).distinct().collect(Collectors.toList());
System.out.println(courses);
结果:
[历史, 数学, 地理, 物理, 英语]
如果本文对你有帮助,请不要吝啬你的点赞,求赞求收藏♥♥♥