Collectors.toList() Collectors.toSet() Collectors.toMap()
这些肯定是冰山一角 就抽时间整理一下 Collectors 方法文档 留作查看
ToXXX 系列/收集、转换
这个系列基本都是收集器 将数据收起来 三大类 就是 List、Set、Map 这个我们会比较常用 每种类型大概的说一下
Collectors.toList()
官方解释:返回将输入元素累积到新列表中的收集器。不保证返回的 List 的类型、可变性、可序列化性或线程安全性
;如果需要对返回的列表进行更多控制,请使用 toCollection(Supplier)。
意思大家都能理解 就是将数据放到一个新的容器里面 会生成一个新的List 不能保证返回的 List 的类型、可变性、可序列化性或线程安全性
一般会自动解析 你原始List的泛型 但是不保证一定会有
Collectors.toSet()
官方解释: 返回一个收集器,它将输入元素累积到一个新的 Set 中
。不保证返回的 Set 的类型、可变性、可序列化性或线程安全性;如果需要对返回的 Set 进行更多控制,请使用 toCollection(Supplier)。这是一个无序收集器。
就是一个去重的集合 下图中 数字 8 被去重
Collectors.toMap()
这是比较有意思的 也是我比较常用的 我就细一点记录
toMap(Key,Value)
最简单的一种用法 我们可以选取对象中的 一个值作为Key 可以选择整个对象作为Value。也可以是某一个值 我个人比较常用的一个模式 但是熟悉的人应该都能看出来 下面这段代码 是错误的
List<EmailPushRecord> list = new ArrayList<>();
EmailPushRecord emailPushRecord = new EmailPushRecord().builder()
.sendTime("2022-09-13")
.isSendSuccess(1)
.messageNumber(123)
.build();
list.add(emailPushRecord);
EmailPushRecord pushRecord = new EmailPushRecord();
BeanUtils.copyProperties(emailPushRecord, pushRecord);
pushRecord.setIsSendSuccess(2);
list.add(pushRecord);
Map<Integer, EmailPushRecord> map = list.stream().collect(Collectors.toMap(EmailPushRecord::getMessageNumber, Function.identity()));
正常结果 是这个样子
但是上述代码中 我的两个对象中的messageNumber是重复的 所以就会出现错误
Duplicate key 也就是说 最简单的模式 这个的Key 是不能重复的 但是我们有时候不能保证我们的Key 不会重复 我们就要使用下面另一种方法
toMap(Function<? super T, ? extends K> keyMapper,Function<? super T, ? extends U> valueMapper)
toMap(Key,Value,指定策略)
这种方法可以解决Key重复问题 我们可以指定 当Key出现冲突的时候 使用 V2 覆盖 V1的值 也可以保留 V1的值 抛弃V2的值 也就是key相同 我们可以选择覆盖或者保留
代码如下:
List<EmailPushRecord> list = new ArrayList<>();
EmailPushRecord emailPushRecord = new EmailPushRecord().builder()
.sendTime("2022-09-13")
.isSendSuccess(1)
.messageNumber(123)
.build();
list.add(emailPushRecord);
EmailPushRecord pushRecord = new EmailPushRecord();
BeanUtils.copyProperties(emailPushRecord, pushRecord);
pushRecord.setIsSendSuccess(2);
list.add(pushRecord);
Map<Integer, EmailPushRecord> cover = list.stream()
.collect(Collectors.toMap(i -> i.getMessageNumber(),
Function.identity(), (v1, v2) -> v2));
Map<Integer, EmailPushRecord> retain = list.stream()
.collect(Collectors.toMap(i -> i.getMessageNumber(),
Function.identity(), (v1, v2) -> v1));
上述代码可以看出来 结果是不同的 原因就是
这个就解决了 我们使用的时候出现Key 重复报错的问题 可以指定 覆盖策略
toMap(Key,Value,指定策略,指定Map 类型)
LinkedHashMap<Integer, EmailPushRecord> linkedHashMap = list.stream().
collect(Collectors.toMap(EmailPushRecord::getMessageNumber,
Function.identity(),
((first, second) -> first),
LinkedHashMap::new));
这个就是一眼懂了 根据需要去使用 不同的特性
一个参数 返回map 可以指定key 和Value 但是key冲突会报错
两个参数 返回map 可以设置Key覆盖策略 Key 重复不会报错
三个参数 返回Map 有上面两个的属性 还能执行返回Map的类型前面两个 都是默认的HashMap
Collectors.toCollection()
官方解释: 返回一个收集器,它将输入元素按照遇到的顺序累积到一个新的集合中。集合由提供的工厂创建。
说实话 没看懂什么意思 这种官方解释翻译过来 就不太好理解了 个人感觉
T-输入元素的类型
C-结果集合的类型
供应商:结果供应商
collectionFactory-返回适当类型的新的空Collection的供应商
可以在 内部去指定结合 ArrayList 、 TreeSet、LinkedList
一般这个都是做一些深层次的数据处理 比如去重
Collectors.toConcurrentMap
这基本都和上面的 toMap 相同 但是这个是无序的 并发的Map 在并发的环境下使用
toMap
-> 是一个非并发收集器
toConcurrentMap
-> 是一个并发收集器(这可以从它们的特性上看出来).
不同的是,toMap 会创建多个中间结果,然后会合并在一起(这样的 Collector 的 Supplier 会被调用多次),而 toConcurrentMap 会创建一个单个结果,每个线程都会向它抛出结果(这样的收集器的供应商只会被调用一次)
toMap 将通过合并多个中间结果(多次调用该收集器的供应商以及组合器)以遇到顺序将值插入结果地图中**
toConcurrentMap 将通过将所有元素扔到一个公共结果容器(在本例中为 ConcurrentHashMap)以任何顺序(未定义)收集元素.供应商只被调用一次,累加器被多次调用,而组合器从不被调用.
这个不做过多讲解 就是相对于toMap 是线程安全的 相当于 HashMap 和 ConcurrentHashMap 区别的思想
GroupingByXXX() 系列/分组
Collectors.groupingBy(EmailPushRecord::getMessageNumber)
这个就是通过你的标示给你进行分组 还很很有用的开发中经常使用 可以看到下图生成的结果 一个Key 指向一个List
Map<Integer, List<EmailPushRecord>> collect = list.stream().collect(Collectors.groupingBy(EmailPushRecord::getMessageNumber));
Collectors.groupingBy(字段,归约条件)
groupingBy(Function<? super T, ? extends K> classifier,
Collector<? super T, A, D> downstream)
这个也比较有意思 我们先看一下写法
Map<Integer, List<String>> setMap = list.stream().collect(Collectors.groupingBy(EmailPushRecord::getMessageNumber, Collectors.mapping(EmailPushRecord::getSendTime, toList())));
上述 和 类型一相比 有一个新的东西 Collectors.mapping 这个东西 我是理解为相当于 Sql中的Select 平常喜欢用 Mybatis-Plus 的人应该知道 MP有一个.select() 的函数用来查询我们需要的字段 Collectors.mapping也有点相同思想 我们可以对分组的结果集进一步处理 我理解的主要有两点
用于返回想要的字段 过滤掉冗余的字段
用于指定储存容器 如 toSet()、toList()
官方例子:
例如,计算每个城市的人的姓氏集合:
Map<City, Set<String>> namesByCity = people.stream().collect(groupingBy(Person::getCity,
mapping(Person::getLastName, toSet ())));
Collectors.groupingBy(字段,map类型,归约条件)
Collector<T, ?, M> groupingBy(Function<? super T, ? extends K> classifier,
Supplier<M> mapFactory,---> Map工厂
Collector<? super T, A, D> downstream)
这个其实就是指定 Map 的类型 默认是HashMap 我们如果需要返回有序的 可以指定为TreeMap
Map<Integer, Set<String>> setMap = list.stream()
.collect(Collectors.groupingBy(EmailPushRecord::getMessageNumber
,TreeMap::new, Collectors.mapping(EmailPushRecord::getSendTime, toSet())));
注意看setMap 的类型是 TreeMap
根据不同需求可以使用不同的Map
这里一定要是 Map下面的 派生类 TreeMap、HashMap等等
官方例子:
例如,要计算每个城市的人的姓氏集合,城市名称是排序的:
{Map<City, Set<String>> namesByCity = people.stream()
.collect (groupingBy(Person::getCity
, TreeMap::new, mapping(Person::getLastName, toSet())));
Collectors.groupingByConcurrent 用法一样 适合在并发下使用 不再进行记录
类型一:简单的分组 默认Map 为hashMap 数据结构key->List
类型二:默认Map为HashMap 可以指定返回的字段 和 集合类型
类型三:在类型二的基础上可以更改Map的类型
Collectors.joining()系列 组装/转换
这个东西的常用在集合数据转换成 String 类型的时候 有点像 String.join 主要提供了三种方式
Collectors.joining() 基础款
List<String> jo = new ArrayList<>();
for (int i = 0; i < 10; i++) {
jo.add(String.valueOf(i));
String collect = jo.stream().collect(Collectors.joining());
String join = String.join("", jo);
System.out.println(join);
System.out.println(collect);
基本上没有什么区别 除了 String.Join 需要多加一个引号 但是仅仅处理 这种简单的数据数据类型 感觉上 有点鸡肋 但是 Stream 主要是集合杀手 所以 Collectors.joining()可以支持对象类型的数据
String collect = list.stream().map(EmailPushRecord::getSendTime)
.collect(Collectors.joining());
Collectors.joining("分割符") 一层加强
joining(CharSequence delimiter)
String collect = list.stream().map(EmailPushRecord::getSendTime)
.collect(Collectors.joining("_"));
Collectors.joining("分割符",前缀,后缀) 二层加强
joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix)
String collect = list.stream().map(EmailPushRecord::getSendTime)
.collect(Collectors.joining("_", "@", "#"));
类型一 不增加分割符 将数据转成一个String 123
类型二 增加分割符 将数据转化成一个通过分割符分割的String 1,2,3
类型三 增加前后缀 将数据转化成一个通过分割符分割的String 且有前后缀 @1,2,3#
可以结合Stream 操作object中的单个字段
Collectors.counting() 返回收集器个数
这个结合 Stream 自带的方法 比如 去重、过滤 才有点意思
Long collect = list.stream()
.filter(item -> item.getIsSendSuccess() > 1).collect(Collectors.counting());
System.out.println(collect);
这个就看 个人需求了。如果希望拿到处理后的数据的个数 可以尝试使用这个
maxBy minBy 最大值最小值
maxBy
EmailPushRecord record = list.stream()
.collect(Collectors
.maxBy(Comparator.comparingInt(EmailPushRecord::getIsSendSuccess))).get();
Integer integer = list.stream()
.map(EmailPushRecord::getIsSendSuccess).max(Integer::compareTo).get();
两种写法没有什么区别 可以使用 Stream().max() 也可以使用 另一种 目前没发现有什么特别之处 不再详细说明 后续研究特别之处后再做更新 minBy 取最小值 不再讲解
Collectrs.averagingXXX 平均值
List<Double> doubles = Arrays.asList(1.1, 2.2, 3.3, 4.4, 5.5);
Double aDouble = doubles.stream().collect(Collectors.averagingDouble(s -> s));
System.out.println(aDouble);
这种有几种类型
averagingDouble、
averagingLong、
averagingInt
都是明面意思 要注意的是 数据类型 强转的时候精度丢失的问题
Collectors.summingXXX 求和
List<Double> doubles = Arrays.asList(1.1, 2.2, 3.3, 4.4, 5.5);
Double aDouble = doubles.stream().collect(Collectors.summingDouble(s -> s));
doubles.stream().collect(Collectors.toList());
System.out.println(aDouble);
这个就是求和 需要注意使用的类型 防止出现精度问题
Stream 自带的也有 看个人习惯使用
Double aDouble = doubles.stream().mapToDouble(s -> s).sum();
Collectors.reducing() 数据运算
reducing(BinaryOperator op)一个参数
List<Integer> testData = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
Integer integer = testData.stream().collect(Collectors.reducing((prev, cur) -> {
System.out.println("prev==》" + prev + "cur==》" + cur);
return prev + cur;
})).get();
System.out.print(integer);
List<Integer> testData = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
Integer integer = testData.stream().collect(Collectors.reducing((prev, cur) -> {
System.out.println("prev==》" + prev + "cur==》" + cur);
return prev / cur;
})).get();
System.out.print(integer);
可以对数据进行加减乘除操作
reducing(T identity, BinaryOperator op)二个参数
T identity 返回类型T初始值
BinaryOperator op 归集操作函数 输入参数T返回T
最终结果 加上 前面的identity 这里是负数所以相当于减
List<Integer> testData = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
Integer integer = testData.stream().collect(Collectors.reducing(-20, (prev, cur) -> {
System.out.println("prev==》" + prev + "cur==》" + cur);
return prev + cur;
System.out.print(integer);
reducing(U identity,Function<? super T, ? extends U> mapper,BinaryOperator op) 三个参数
U identity 最终返回类型U初始值
Function<? super T, ? extends U> mapper 将输入参数T转换成返回类型U的函数\
BinaryOperator op 归集操作函数 输入参数U返回U
List<Integer> testData = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
String collect = testData.stream().collect(Collectors.reducing("测试转换", in -> in + "", (perv, cur) -> perv + "," + cur));
System.out.print(collect);
这个可以用来拼接 个人也没用过 具体使用场景还需要琢磨一下
Collectrs.partitioningBy() 分类规约
partitioningBy(Predicate<? super T> predicate) 一个参数
这个感觉是有点像filter 的增强版 可以通过条件返回 符合条件的和不符合条件的
partitioningBy(Predicate<? super T> predicate,Collector<? super T, A, D> downstream) 两个参数
可以使用Collectors的一些属性 指定返回的数据和接收集合等等
List<Integer> testData = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
Map<Boolean, Long> collect = testData.stream().collect(Collectors.partitioningBy(num -> (num > 3), Collectors.counting()));
Map<Boolean, Set<Integer>> setMap = testData.stream().collect(Collectors.partitioningBy(num -> (num > 3), Collectors.toSet()));
System.out.print(collect);
一个参数 可以做的是通过你设置的条件 返回数据集 符合条件key->true 不符合条件 Key->false
两个参数 可以使用Collectors的一些属性 指定返回的数据和接收集合等等
搞完了 也算更加了解了 对于其中的一些方法使用也算是拿捏了 这个还是比较经常用到的 扒光后豁然开朗 这个圣诞老人胡子真白 不好意思评论的话 点个赞吧 🥰🥰🥰
全栈开发 @ 🏆糊涂码专家
244.5k
粉丝