一文掌握 Java8 Stream 中 Collectors 的 24 个操作

元素数量:counting

=============

这个比较简单,就是统计聚合结果的元素数量:

// 3

students.stream().collect(Collectors.counting())

平均值:averagingDouble、averagingInt、averagingLong

==============================================

这几个方法是计算聚合元素的平均值,区别是输入参数需要是对应的类型。

比如,求学生的分数平均值,因为分数是double类型,所以在不转类型的情况下,需要使用averagingDouble:

// 22.123

students.stream().collect(Collectors.averagingDouble(Student::getScore))

如果考虑转换精度,也是可以实现:

// 22.0

students.stream().collect(Collectors.averagingInt(s -> (int)s.getScore()))

// 22.0

students.stream().collect(Collectors.averagingLong(s -> (long)s.getScore()))

如果是求学生的平均年龄,因为年龄是int类型,就可以随意使用任何一个函数了:

// 11.0

students.stream().collect(Collectors.averagingInt(Student::getAge))

// 11.0

students.stream().collect(Collectors.averagingDouble(Student::getAge))

// 11.0

students.stream().collect(Collectors.averagingLong(Student::getAge))

注意:这三个方法的返回值都是Double类型。

和:summingDouble、summingInt、summingLong

======================================

这三个方法和上面的平均值方法类似,也是需要注意元素的类型,在需要类型转换时,需要强制转换:

// 66

students.stream().collect(Collectors.summingInt(s -> (int)s.getScore()))

// 66.369

students.stream().collect(Collectors.summingDouble(Student::getScore))

// 66

students.stream().collect(Collectors.summingLong(s -> (long)s.getScore()))

但是对于不需要强制转换的类型,可以随意使用任何一个函数:

// 33

students.stream().collect(Collectors.summingInt(Student::getAge))

// 33.0

students.stream().collect(Collectors.summingDouble(Student::getAge))

// 33

students.stream().collect(Collectors.summingLong(Student::getAge))

注意:这三个方法返回值和平均值的三个方法不一样,summingDouble返回的是Double类型、summingInt返回的是Integer类型,summingLong返回的是Long类型。

最大值/最小值元素:maxBy、minBy

=====================

顾名思义,这两个函数就是求聚合元素中指定比较器中的最大/最小元素。比如,求年龄最大/最小的Student对象:

// Optional[Student(id=3, name=王五, birthday=2011-03-03, age=10, score=32.123)],注意返回类型是Optional

students.stream().collect(Collectors.minBy(Comparator.comparing(Student::getAge)))

// Optional[Student(id=1, name=张三, birthday=2009-01-01, age=12, score=12.123)],注意返回类型是Optional

students.stream().collect(Collectors.maxBy(Comparator.comparing(Student::getAge)))

从源码可以看出来,这两个方法算是作者给的福利,用于完善数据统计的结果。内部都是封装了reducing方法和BinaryOperator工具类,这些下面会讲到。

public static  Collector<T, ?, Optional> maxBy(Comparator<? super T> comparator) {

return reducing(BinaryOperator.maxBy(comparator));

}

public static  Collector<T, ?, Optional> minBy(Comparator<? super T> comparator) {

return reducing(BinaryOperator.minBy(comparator));

}

统计结果:summarizingDouble、summarizingInt、summarizingLong

=====================================================

既然是数据操作,基本上逃不出计数、求平局、求和、最大、最小这几个,所以作者也是很贴心的实现了一组聚合的数据统计方法。

这组方法与求和、求平均的方法类似,都需要注意方法类型。比如,按照分数统计的话,需要进行类型转换:

// IntSummaryStatistics{count=3, sum=66, min=12, average=22.000000, max=32}

students.stream().collect(Collectors.summarizingInt(s -> (int) s.getScore()))

// DoubleSummaryStatistics{count=3, sum=66.369000, min=12.123000, average=22.123000, max=32.123000}

students.stream().collect(Collectors.summarizingDouble(Student::getScore))

// LongSummaryStatistics{count=3, sum=66, min=12, average=22.000000, max=32}

students.stream().collect(Collectors.summarizingLong(s -> (long) s.getScore()))

如果是用年龄统计的话,三个方法通用:

// IntSummaryStatistics{count=3, sum=33, min=10, average=11.000000, max=12}

students.stream().collect(Collectors.summarizingInt(Student::getAge))

// DoubleSummaryStatistics{count=3, sum=33.000000, min=10.000000, average=11.000000, max=12.000000}

students.stream().collect(Collectors.summarizingDouble(Student::getAge))

// LongSummaryStatistics{count=3, sum=33, min=10, average=11.000000, max=12}

students.stream().collect(Collectors.summarizingLong(Student::getAge))

注意:这三个方法返回值不一样,summarizingDouble返回DoubleSummaryStatistics类型,summarizingInt返回IntSummaryStatistics类型,summarizingLong返回LongSummaryStatistics类型。

聚合、分组

=====

聚合元素:toList、toSet、toCollection

==============================

这几个函数比较简单,是将聚合之后的元素,重新封装到队列中,然后返回。比如,得到所有Student的 ID 列表,只需要根据需要的结果类型使用不同的方法即可:

// List: [1, 2, 3]

final List idList = students.stream().map(Student::getId).collect(Collectors.toList());

// Set: [1, 2, 3]

final Set idSet = students.stream().map(Student::getId).collect(Collectors.toSet());

// TreeSet: [1, 2, 3]

final Collection idTreeSet = students.stream().map(Student::getId).collect(Collectors.toCollection(TreeSet::new));

注意:toList方法返回的是List子类,toSet返回的是Set子类,toCollection返回的是Collection子类。我们都知道,Collection的子类包括List、Set等众多子类,所以toCollection更加灵活。

聚合元素:toMap、toConcurrentMap

==========================

这两个方法的作用是将聚合元素,重新组装为Map结构,也就是 k-v 结构。两者用法一样,区别是toMap返回的是Map,toConcurrentMap返回ConcurrentMap,也就是说,toConcurrentMap返回的是线程安全的 Map 结构。

比如,我们需要聚合Student的 id:

// {1=Student(id=1, name=张三, birthday=2009-01-01, age=12, score=12.123), 2=Student(id=2, name=李四, birthday=2010-02-02, age=11, score=22.123), 3=Student(id=3, name=王五, birthday=2011-03-03, age=10, score=32.123)}

final Map<String, Student> map11 = students.stream()

.collect(Collectors.toMap(Student::getId, Function.identity()));

但是,如果 id 有重复的,会抛出java.lang.IllegalStateException: Duplicate key异常,所以,为了保险起见,我们需要借助toMap另一个重载方法:

// {1=Student(id=1, name=张三, birthday=2009-01-01, age=12, score=12.123), 2=Student(id=2, name=李四, birthday=2010-02-02, age=11, score=22.123), 3=Student(id=3, name=王五, birthday=2011-03-03, age=10, score=32.123)}

final Map<String, Student> map2 = students.stream()

.collect(Collectors.toMap(Student::getId, Function.identity(), (x, y) -> x));

可以看到,toMap有不同的重载方法,可以实现比较复杂的逻辑。比如,我们需要得到根据 id 分组的Student的姓名:

// {1=张三, 2=李四, 3=王五}

final Map<String, String> map3 = students.stream()

.collect(Collectors.toMap(Student::getId, Student::getName, (x, y) -> x));

比如,我们需要得到相同年龄得分最高的Student对象集合:

// {10=Student(id=3, name=王五, birthday=2011-03-03, age=10, score=32.123), 11=Student(id=2, name=李四, birthday=2010-02-02, age=11, score=22.123), 12=Student(id=1, name=张三, birthday=2009-01-01, age=12, score=12.123)}

final Map<Integer, Student> map5 = students.stream()

.collect(Collectors.toMap(Student::getAge, Function.identity(), BinaryOperator.maxBy(Comparator.comparing(Student::getScore))));

所以,toMap可玩性很高。

分组:groupingBy、groupingByConcurrent

==================================

groupingBy与toMap都是将聚合元素进行分组,区别是,toMap结果是 1:1 的 k-v 结构,groupingBy的结果是 1:n 的 k-v 结构。

比如,我们对Student的年龄分组:

// List: {10=[Student(id=3, name=王五, birthday=2011-03-03, age=10, score=32.123)], 11=[Student(id=2, name=李四, birthday=2010-02-02, age=11, score=22.123)], 12=[Student(id=1, name=张三, birthday=2009-01-01, age=12, score=12.123)]}

final Map<Integer, List> map1 = students.stream().collect(Collectors.groupingBy(Student::getAge));

// Set: {10=[Student(id=3, name=王五, birthday=2011-03-03, age=10, score=32.123)], 11=[Student(id=2, name=李四, birthday=2010-02-02, age=11, score=22.123)], 12=[Student(id=1, name=张三, birthday=2009-01-01, age=12, score=12.123)]}

final Map<Integer, Set> map12 = students.stream().collect(Collectors.groupingBy(Student::getAge, Collectors.toSet()));

既然groupingBy也是分组,是不是也能够实现与toMap类似的功能,比如,根据 id 分组的Student:

// {1=Student(id=1, name=张三, birthday=2009-01-01, age=12, score=12.123), 2=Student(id=2, name=李四, birthday=2010-02-02, age=11, score=22.123), 3=Student(id=3, name=王五, birthday=2011-03-03, age=10, score=32.123)}

final Map<String, Student> map3 = students.stream()

.collect(Collectors.groupingBy(Student::getId, Collectors.collectingAndThen(Collectors.toList(), list -> list.get(0))));

为了对比,把toMap的写法放在这:

// {1=Student(id=1, name=张三, birthday=2009-01-01, age=12, score=12.123), 2=Student(id=2, name=李四, birthday=2010-02-02, age=11, score=22.123), 3=Student(id=3, name=王五, birthday=2011-03-03, age=10, score=32.123)}

final Map<String, Student> map2 = students.stream()

.collect(Collectors.toMap(Student::getId, Function.identity(), (x, y) -> x));

如果想要线程安全的Map,可以使用groupingByConcurrent。

分组:partitioningBy

=================

partitioningBy与groupingBy的区别在于,partitioningBy借助Predicate断言,可以将集合元素分为true和false两部分。比如,按照年龄是否大于 11 分组:

// List: {false=[Student(id=2, name=李四, birthday=2010-02-02, age=11, score=22.123), Student(id=3, name=王五, birthday=2011-03-03, age=10, score=32.123)], true=[Student(id=1, name=张三, birthday=2009-01-01, age=12, score=12.123)]}

final Map<Boolean, List> map6 = students.stream().collect(Collectors.partitioningBy(s -> s.getAge() > 11));

// Set: {false=[Student(id=3, name=王五, birthday=2011-03-03, age=10, score=32.123), Student(id=2, name=李四, birthday=2010-02-02, age=11, score=22.123)], true=[Student(id=1, name=张三, birthday=2009-01-01, age=12, score=12.123)]}

final Map<Boolean, Set> map7 = students.stream().collect(Collectors.partitioningBy(s -> s.getAge() > 11, Collectors.toSet()));

链接数据:joining

============

这个方法对String类型的元素进行聚合,拼接成一个字符串返回,作用与java.lang.String#join类似,提供了 3 个不同重载方法,可以实现不同的需要。比如:

// javagosql

Stream.of(“java”, “go”, “sql”).collect(Collectors.joining());

// java, go, sql

Stream.of(“java”, “go”, “sql”).collect(Collectors.joining(", "));

// 【java, go, sql】

Stream.of(“java”, “go”, “sql”).collect(Collectors.joining(", ", “【”, “】”));

操作链:collectingAndThen

=====================

这个方法在groupingBy的例子中出现过,它是先对集合进行一次聚合操作,然后通过Function定义的函数,对聚合后的结果再次处理。

比如groupingBy中的例子:

// {1=Student(id=1, name=张三, birthday=2009-01-01, age=12, score=12.123), 2=Student(id=2, name=李四, birthday=2010-02-02, age=11, score=22.123), 3=Student(id=3, name=王五, birthday=2011-03-03, age=10, score=32.123)}

final Map<String, Student> map3 = students.stream()

.collect(Collectors.groupingBy(Student::getId, Collectors.collectingAndThen(Collectors.toList(), list -> list.get(0))));

显示将结果聚合成List列表,然后取列表的第 0 个元素返回,通过这种方式,实现 1:1 的 map 结构。

再来一个复杂一些的,找到聚合元素中年龄数据正确的Student列表:

// [],结果为空,是因为例子中所有人的年龄都是对的

students.stream()

.collect(

Collectors.collectingAndThen(Collectors.toList(), (

list -> list.stream()

.filter(s -> (LocalDate.now().getYear() - s.getBirthday().getYear()) != s.getAge())

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

总结

虽然面试套路众多,但对于技术面试来说,主要还是考察一个人的技术能力和沟通能力。不同类型的面试官根据自身的理解问的问题也不尽相同,没有规律可循。

上面提到的关于这些JAVA基础、三大框架、项目经验、并发编程、JVM及调优、网络、设计模式、spring+mybatis源码解读、Mysql调优、分布式监控、消息队列、分布式存储等等面试题笔记及资料

有些面试官喜欢问自己擅长的问题,比如在实际编程中遇到的或者他自己一直在琢磨的这方面的问题,还有些面试官,尤其是大厂的比如 BAT 的面试官喜欢问面试者认为自己擅长的,然后通过提问的方式深挖细节,刨根到底。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
ommunity.csdnimg.cn/images/e5c14a7895254671a72faed303032d36.jpg" alt=“img” style=“zoom: 33%;” />

总结

虽然面试套路众多,但对于技术面试来说,主要还是考察一个人的技术能力和沟通能力。不同类型的面试官根据自身的理解问的问题也不尽相同,没有规律可循。

[外链图片转存中…(img-gOHn2WtM-1713434088642)]

[外链图片转存中…(img-MDOnT0iy-1713434088642)]

上面提到的关于这些JAVA基础、三大框架、项目经验、并发编程、JVM及调优、网络、设计模式、spring+mybatis源码解读、Mysql调优、分布式监控、消息队列、分布式存储等等面试题笔记及资料

有些面试官喜欢问自己擅长的问题,比如在实际编程中遇到的或者他自己一直在琢磨的这方面的问题,还有些面试官,尤其是大厂的比如 BAT 的面试官喜欢问面试者认为自己擅长的,然后通过提问的方式深挖细节,刨根到底。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值