简介
Collectors
- 将数据收集进一个列表
Collectors.toList();
Collectors.toSet();
- 将数据收集进一个集合
Collectors.toMap();
- 用自定义的实现Collection的数据结构收集
Collectors.toCollection();
- 集合元素拼接
Collectors.Joining();
collectingAndThen(); // 收集之后继续做一些处理。
.()中一般是Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(AssetInfo::getNumber)))
public static class HeaderDto implements Serializable {
private static final long serialVersionUID = 89423674447742L;
private String version;
@JSONField(name = "sys-code")
private String sysCode;
@JSONField(name = "biz-id")
private String bizId;
@JSONField(name = "biz-type")
private String bizType;
private Long timestamp;
private String sign;
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public String getSysCode() {
return sysCode;
}
public void setSysCode(String sysCode) {
this.sysCode = sysCode;
}
public String getBizId() {
return bizId;
}
public void setBizId(String bizId) {
this.bizId = bizId;
}
public String getBizType() {
return bizType;
}
public void setBizType(String bizType) {
this.bizType = bizType;
}
public Long getTimestamp() {
return timestamp;
}
public void setTimestamp(Long timestamp) {
this.timestamp = timestamp;
}
public String getSign() {
return sign;
}
public void setSign(String sign) {
this.sign = sign;
}
}
public static void main(String[] args) {
List<HeaderDto> list = new ArrayList<>();
HeaderDto dto1 = new HeaderDto();
dto1.setBizId("01");
dto1.setBizType("机构A");
HeaderDto dto2 = new HeaderDto();
dto2.setBizId("01");
dto2.setBizType("机构A");
HeaderDto dto3 = new HeaderDto();
dto3.setBizId("01");
dto3.setBizType("机构B");
HeaderDto dto4 = new HeaderDto();
dto4.setBizId("02");
dto4.setBizType("机构C");
list.add(dto1);
list.add(dto2);
list.add(dto3);
list.add(dto4);
// 分组后, 将value去重后拼接成 字符串,两个元素之间用 , 分割
Map<String, String> filter = list.stream()
.collect(Collectors.groupingBy(HeaderDto::getBizId,
Collectors.mapping(HeaderDto::getBizType,
Collectors.collectingAndThen(Collectors.toSet(), e -> String.join(",", e)))));
// {01=机构A,机构B, 02=机构C}
System.out.println(filter);
}
- 元素聚合
maxBy,
minBy,
summingInt,
averagingDouble
mapping
- 分组
Map<Long, List<String>> collect = list.stream().collect(
Collectors.toMap(SettleRegisterInfo::getId,
item -> Arrays.asList(item.getChannel().split(","))));
- 累计操作
sum
示例
一、collect
collect 是一个终端操作,它接收的参数是将流中的元素累积到汇总结果的各种方式(称为收集器)
二、预定义收集器
包括将流元素归约和汇总到一个值.如下:
工厂方法 | 返回类型 | 用于 | 示例 |
---|---|---|---|
toList | List < T > | 把流中所有元素收集到List中 | List< Menu > menus=Menu.getMenus.stream().collect(Collectors.toList()) ; |
toSet | Set< T > | 把流中所有元素收集到Set中,删除重复项 | Set< Menu > menus=Menu.getMenus.stream().collect(Collectors.toSet()); |
toCollection | Collection< T > | 把流中所有元素收集到给定的供应源创建的集合中 | ArrayList< Menu > menus=Menu.getMenus.stream().collect(Collectors.toCollection(ArrayList::new)); |
counting | Long | 计算流中元素个数 | Long count=Menu.getMenus.stream().collect(counting); |
SummingInt | Integer | 对流中元素的一个整数属性求和 | Integer count=Menu.getMenus.stream().collect(summingInt(Menu::getCalories)); |
averagingInt | Double | 计算流中元素integer属性的平均值 | Double averaging=Menu.getMenus.stream().collect(averagingInt(Menu::getCalories)); |
Joining | String | 连接流中每个元素的toString方法生成的字符串 | String name=Menu.getMenus.stream().map(Menu::getName).collect(joining(“, ”)); |
maxBy | Optional< T > | 一个包裹了流中按照给定比较器选出的最大元素的optional,如果为空返回的是Optional.empty(); | Optional< Menu > fattest=Menu.getMenus.stream().collect(maxBy(Menu::getCalories)) |
minBy | Optional< T > | 一个包裹了流中按照给定比较器选出的最大元素的optional,如果为空返回的是Optional.empty(); | Optional< Menu > lessest=Menu.getMenus.stream().collect(minBy(Menu::getCalories)); |
Reducing | 归约操作产生的类型 | 从一个作为累加器的初始值开始,利用binaryOperator与流中的元素逐个结合,从而将流归约为单个值 | int count=Menu.getMenus.stream().collect(reducing(0,Menu::getCalories,Integer::sum)); |
collectingAndThen | 转换函数返回的类型 | 包裹另一个转换器,对其结果应用转换函数 | Int count=Menu.getMenus.stream().collect(collectingAndThen(toList(),List::size)) |
groupingBy | Map<K,List< T >> | 根据流中元素的某个值对流中的元素进行分组,并将属性值做为结果map的键 | Map<Type,List< Menu >> menuType=Menu.getMenus.stream().collect(groupingby(Menu::getType)); |
partitioningBy | Map<Boolean,List< T >> | 根据流中每个元素应用谓语的结果来对项目进行分区 | Map<Boolean,List< Menu >> menuType=Menu.getMenus.stream().collect(partitioningBy(Menu::isType)); |
三,预定义收集器可以用groupby对流中元素进行分组或者用partitioningBy进行分区
四,收集器可以高效的复合起来,进行多级分组,多级分区和归约
五,可以自己实现collector接口进行定义自己的收集器
具体参考代码
List<AssetSnUpdateData> result = List<AssetSnUpdateData>.stream().filter(s -> snList.contains(s.getAssetSn())).collect(Collectors.toList());
Map<Long, List<String>> collect = list.stream()
.collect(Collectors.toMap(SettleRegisterInfo::getId, item -> Arrays.asList(item.getChannel().split(","))));
list.stream.collect(Collectors.toMap(e -> e, e -> 1, (a, b) -> a + b)) // 获得元素出现频率的 Map,键为元素,值为元素出现的次数
.entrySet().stream() // Set<Entry>转换为Stream<Entry>
.filter(entry -> entry.getValue() > 1) // 过滤出元素出现次数大于 1 的 entry
.map(entry -> entry.getKey()) // 获得 entry 的键(重复元素)对应的 Stream
.collect(Collectors.toList()); // 转化为 List
public static void main(String[] args) {
List<AssetInfo> associatedAssetInfoList = new ArrayList<>();
AssetInfo assetInfo = new AssetInfo();
assetInfo.setNumber("001");
assetInfo.setOldNumber("001001");
AssetInfo assetInfo1 = new AssetInfo();
assetInfo1.setNumber("001");
assetInfo1.setOldNumber("001002");
AssetInfo assetInfo2 = new AssetInfo();
assetInfo2.setNumber("002");
assetInfo2.setOldNumber("001001");
AssetInfo assetInfo3 = new AssetInfo();
assetInfo3.setNumber("003");
assetInfo3.setOldNumber("001001");
associatedAssetInfoList.add(assetInfo);
associatedAssetInfoList.add(assetInfo1);
associatedAssetInfoList.add(assetInfo2);
associatedAssetInfoList.add(assetInfo3);
if (!CollectionUtils.isEmpty(associatedAssetInfoList)) {
// todo: 根据资产编号去重
associatedAssetInfoList = associatedAssetInfoList.stream().collect(
Collectors.collectingAndThen(
Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(AssetInfo::getNumber))),
ArrayList::new)
);
}
associatedAssetInfoList.forEach(e -> System.out.println(e));
// 1-todo: 根据资产的编号和旧资产编号进行分组,即多属性进行分组
Map<List<String>, List<AssetInfo>> collect = associatedAssetInfoList.stream()
.collect(Collectors.groupingBy(f -> Arrays.asList(f.getNumber(),f.getOldNumber())));
// 1-todo: 实现原理不同,上面的是直接根据多个属性进行分组,下面是先根据number分组,再根据oldNumber分组,最终结果是map嵌套map
Map<String, Map<String, List<AssetInfo>>> collect = associatedAssetInfoList.stream()
.collect(Collectors.groupingBy(AssetInfo::getNumber, Collectors.groupingBy(AssetInfo::getOldNumber)));
// 2-map收集资产分类 -> 去重后 -> 收集后转换为集合
List<String> typeList = list.stream().map(TypeNameModelImportData::getTypeName).distinct()
.collect(Collectors.toList()); // map收集资产分类 -> 去重后 -> 收集后转换为集合
// 2-map收集资产分类的名称 -> 取set集合方式来去重
Set<String> typeSet = valueList.stream().map(TypeNameModelImportData::getTypeName)
.collect(Collectors.toSet()); // map收集资产分类的名称 -> 去重 取set集合
Map<String, Long> map = p2pIdList.stream().filter(Objects::nonNull).collect(
Collectors.groupingBy(p2pId -> p2pId, Collectors.counting())); // todo: 过滤器 Objects::nonNull 过滤掉null值 -> 根据p2pId分组
Map<String, Integer> assetNameKV = allNameList.stream()
.collect(Collectors.toMap(SysAssetName::getName, SysAssetName::getId)); // excel中所有的资产名称对应的 name-id
}
java7-8的代码写法变化
在 Java 7 中,如果要发现 type 为 grocery 的所有交易,然后返回以交易值降序排序好的交易 ID 集合,我们需要这样写:
清单 1. Java 7 的排序、取值实现
List<Transaction> groceryTransactions = new Arraylist<>();
for(Transaction t: transactions){
if(t.getType() == Transaction.GROCERY){
groceryTransactions.add(t);
}
}
Collections.sort(groceryTransactions, new Comparator(){
public int compare(Transaction t1, Transaction t2){
return t2.getValue().compareTo(t1.getValue());
}
});
List<Integer> transactionIds = new ArrayList<>();
for(Transaction t: groceryTransactions){
transactionsIds.add(t.getId());
}
而在 Java 8 使用 Stream,代码更加简洁易读;而且使用并发模式,程序执行速度更快。
清单 2. Java 8 的排序、取值实现
List<Integer> transactionsIds = transactions.parallelStream()
.filter(t -> t.getType() == Transaction.GROCERY)
.sorted(comparing(Transaction::getValue).reversed())
.map(Transaction::getId).collect(toList());
reduce:它提供一个起始值(种子),然后依照运算规则(BinaryOperator),和前面 Stream 的第一个、第二个、第 n 个元素组合。
字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce。
例如 Stream 的 sum 就相当于
Integer sum = integers.reduce(0, (a, b) -> a+b);
// 或
Integer sum = integers.reduce(0, Integer::sum);
清单 3. reduce 的用例
// 字符串连接,concat = "ABCD"
String concat = Stream.of("A", "B", "C", "D").reduce("", String::concat);
// 求最小值,minValue = -3.0
double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MAX_VALUE, Double::min);
// 求和,sumValue = 10, 有起始值
int sumValue = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum);
// 求和,sumValue = 10, 无起始值
sumValue = Stream.of(1, 2, 3, 4).reduce(Integer::sum).get();
// 过滤,字符串连接,concat = "ace"
concat = Stream.of("a", "B", "c", "D", "e", "F")
.filter(x -> x.compareTo("Z") > 0).reduce("", String::concat);
清单 15: 上面代码例如第一个示例的 reduce(),第一个参数(空白字符)即为起始值,第二个参数(String::concat)为 BinaryOperator。
这类有起始值的 reduce() 都返回具体的对象。而对于第四个示例没有起始值的 reduce(),
由于可能没有足够的元素,返回的是 Optional,请留意这个区别。
延伸
Java8中Collectors工具类中得partitioningBy与groupingBy区别
https://blog.youkuaiyun.com/qq_28336067/article/details/79215512
partitioningBy和groupingBy都是用于将数据进行分组的函数。
两者的区别要从函数的签名看起。
partitioningBy函数的定义如下
public static <T>
Collector<T, ?, Map<Boolean, List<T>>> partitioningBy(Predicate<? super T> predicate) {
return partitioningBy(predicate, toList());
}
可以看出函数的参数一个Predicate接口,那么这个接口的返回值是boolean类型的,也只能是boolean类型,然后他的返回值是Map的key是boolean类型,也就是这个函数的返回值只能将数据分为两组也就是ture和false两组数据。
groupingBy函数的定义,仅查看与partitioningBy参数相同的定义
public static <T, K> Collector<T, ?, Map<K, List<T>>>
groupingBy(Function<? super T, ? extends K> classifier) {
return groupingBy(classifier, toList());
}
groupingBy的函数参数为Function然后他的返回值也是Map,但是他的key是泛型,那么这个分组就会将数据分组成多个key的形式。