共计 19839 个字符,预计需要花费 50 分钟才能阅读完成。
原创:扣钉日记(微信公众号 ID:codelogs),欢送分享,非公众号转载保留此申明。
简介
日常编程工作中,Java 汇合会常常被应用到,且常常须要对汇合做一些相似过滤、排序、对象转换之类的操作。
为了简化这类操作,Java8 增加了一套新的 Stream API,应用形式就像写 SQL 一样,大大简化了这类解决的实现代码量与可读性。
根底 Stream 函数
比方,咱们要查问双 11 期间交易额最大的 10 笔订单的用户信息,用 SQL 实现的话,大抵如下:
select user_id, user_name
from order
where pay_time >= '2022-11-01' and pay_time < '2022-12-01'
order by goods_amount desc
limit 10;
这种解决逻辑,不必 Stream API,实现代码大抵如下:
public static List<User> getTop10Users() throws ParseException {List<Order> orders = getOrders();
// 过滤出双 11 订单
List<Order> filteredOrders = new ArrayList<>();
long begin = DateUtils.parseDate("2022-11-01", "yyyy-MM-dd").getTime();
long end = DateUtils.parseDate("2022-12-01", "yyyy-MM-dd").getTime();
for (Order order : orders) {if(order.getPayTime().getTime() >= begin && order.getPayTime().getTime() < end) {filteredOrders.add(order);
}
}
// 按订单金额倒序排序
filteredOrders.sort(Comparator.comparing(Order::getGoodsAmount).reversed());
// 取前 10 名订单,组装出用户信息
List<User> users = new ArrayList<>();
Iterator<Order> it = filteredOrders.iterator();
for (int i = 0; i < 10 && it.hasNext(); i++) {Order order = it.next();
users.add(new User(order.getUserId(), order.getUserName()));
}
return users;
}
下面代码与 SQL 的逻辑是一样的,但能够发现,下面代码的可了解性比 SQL 差很多,起因是 SQL 应用的是含意更加靠近用意的申明式语法,而上述代码如果没有很好的正文的话,则须要你的大脑像 CPU 一样,将各种指令执行一遍才明确大略用意。
那咱们再用 Stream API 实现一下这个函数看看,如下:
public static List<User> getTop10Users() throws ParseException {List<Order> orders = getOrders();
long begin = DateUtils.parseDate("2022-11-01", "yyyy-MM-dd").getTime();
long end = DateUtils.parseDate("2022-12-01", "yyyy-MM-dd").getTime();
List<User> users = orders.stream()
.filter(order -> order.getPayTime().getTime() >= begin && order.getPayTime().getTime() < end)
.sorted(Comparator.comparing(Order::getGoodsAmount).reversed())
.limit(10)
.map(order -> new User(order.getUserId(), order.getUserName()))
.collect(Collectors.toList());
return users;
}
这段代码我没有加正文,但只有有过一点教训的程序员,都能很快明确它是在做啥,这是因为 Stream API 和 SQL 设计相似,应用的是更加靠近用意的申明式函数,看到函数名就大略明确含意了。
大略解释一下,如下:
stream()
函数用于将汇合转换为 Stream 流对象。filter()
函数过滤 Stream 流中的元素,传入的逻辑表达式则为过滤规定。sorted()
函数排序 Stream 流中的元素,应用传入的 Comparator 比拟元素大小。limit()
函数取前 x 个元素,传入参数指定取的元素个数。map()
函数用于转换 Stream 中的元素为另一类型元素,能够类比于 SQL 从表中查问指定字段时,就如同是创立了一个蕴含这些字段的长期表一样。
Stream 外面的函数大多很简略,就不逐个介绍了,如下:
函数 | 用处 | 类比 SQL |
---|---|---|
map | 转换 Stream 中的元素为另一类型元素 | select x,y,z |
filter | 过滤 Stream 中元素 | where |
sorted | 排序 Stream 中元素 | order by |
limit | 取前 x 个元素 | limit |
distinct | 去重 Stream 中元素 | distinct |
count | 计数 | count(*) |
min | 计算最小值 | min(x) |
max | 计算最大值 | max(x) |
forEach | 生产 Stream 中的每个元素 | – |
toArray | 转换为数组 | – |
findFirst | 获取第 1 个元素 | – |
findAny | 获取任一个元素,与 findFirst 区别是 findAny 可能是数据拆分后多线程解决的,返回值可能不稳固 | – |
allMatch | Stream 中元素全副匹配断定表达式 | – |
anyMatch | Stream 中元素任一匹配断定表达式 | – |
noneMatch | Stream 中元素全副不匹配断定表达式 | – |
peek | 查看通过 Stream 的每个元素,但并不生产元素,个别用于调试目标 | – |
这些是 Stream 比拟根底的用法,上面看看一些更高级的用法吧!
reduce 函数
能够看到 Stream 提供了 min、max 操作,但并没有提供 sum、avg 这样的操作,如果要实现 sum、avg 操作,就能够应用 reduce(迭代)函数来实现,reduce 函数有 3 个,如下:
上面以订单金额的 sum 汇总操作为示例,如下:
带初始值与累加器的 reduce 函数
T reduce(T identity, BinaryOperator<T> accumulator);
汇总示例:
List<Order> orders = getOrders();
BigDecimal sum = orders.stream()
.map(Order::getGoodsAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
其中,reduce 函数的 identity 参数 BigDecimal.ZERO
相当于是初始值,而 accumulator 参数 BigDecimal::add
是一个累加器,将 Stream 中的金额一个个累加起来。
reduce 函数的执行逻辑大抵如下:
无初始值的 reduce 函数
Optional<T> reduce(BinaryOperator<T> accumulator);
汇总示例:
List<Order> orders = getOrders();
BigDecimal sum = orders.stream()
.map(Order::getGoodsAmount)
.reduce(BigDecimal::add)
.orElse(BigDecimal.ZERO);
第 2 个 reduce 函数不传入初始值,只有累加器函数,返回 Optional,因而当 Stream 中没有元素时,它返回的 Optional 没有值,这种状况我应用 Optional.orElse
函数给了一个默认值BigDecimal.ZERO
。
带初始值、累加器、合并器的 reduce 函数
<U> U reduce(U identity,
BiFunction<U, ? super T, U> accumulator,
BinaryOperator<U> combiner);
汇总示例:
List<Order> orders = getOrders();
BigDecimal sum = orders.stream()
.reduce(BigDecimal.ZERO, (s, o) -> s.add(o.getGoodsAmount()), BigDecimal::add);
这个 reduce 函数的累加器和后面的不一样,后面的累加器的迭代元素与汇总后果都是 BigDecimal,而这个累加器的迭代元素是 Order 类型,汇总后果是 BigDecimal 类型,它们能够不一样。
另外,这个 reduce 函数还提供了一个合并器,它是做什么用的?
其实合并器用于并行流场景,当应用多个线程解决数据时,数据拆分给多个线程后,每个线程应用累加器计算出本人的汇总值,而后应用合并器将各个线程的汇总值再次汇总,从而计算出最初后果,执行过程如下图:
应用 reduce 实现 avg
reduce 能够实现 avg,但略微有点繁琐,如下:
@Data
private static class SumCount {
private BigDecimal sum = BigDecimal.ZERO;
private Integer count = 0;
/**
* 累加函数
* @param val
* @return
*/
public SumCount accumulate(BigDecimal val) {this.sum = this.sum.add(val);
this.count++;
return this;
}
/**
* 合并函数
* @param sumCount
* @return
*/
public SumCount merge(SumCount sumCount) {SumCount sumCountNew = new SumCount();
sumCountNew.setSum(this.sum.add(sumCount.sum));
sumCountNew.setCount(this.count + sumCount.count);
return sumCountNew;
}
public Optional<BigDecimal> calAvg(int scale, int roundingMode) {if (count == 0) {return Optional.empty();
}
return Optional.of(this.sum.divide(BigDecimal.valueOf(count), scale, roundingMode));
}
}
List<Order> orders = getOrders();
Optional<BigDecimal> avg = orders.stream()
.map(Order::getGoodsAmount)
.reduce(new SumCount(), SumCount::accumulate, SumCount::merge)
.calAvg(2, BigDecimal.ROUND_HALF_UP);
如上,因为 avg 是由汇总值除以数量计算出来的,所以须要定义一个 SumCount 类来记录汇总值与数量,并实现它的累加器与合并器函数即可。
能够发现,应用 reduce 函数实现 avg 性能,还是有点麻烦的,而且代码可读性不强,大脑须要绕一下才晓得是在求平均数,而 collect 函数就能够很不便的解决这个问题。
collect 函数
Stream API 提供了一个 collect(收集)函数,用来解决一些比较复杂的应用场景,它传入一个收集器 Collector 用来收集流中的元素,并做特定的解决(如汇总),Collector 定义如下:
public interface Collector<T, A, R> {Supplier<A> supplier();
BiConsumer<A, T> accumulator();
BinaryOperator<A> combiner();
Function<A, R> finisher();
Set<Characteristics> characteristics();}
其实,收集器与 reduce 是比拟相似的,只是比 reduce 更加灵便了,如下:
- supplier: 初始汇总值提供器,相似 reduce 中的 identity,只是这个初始值是函数提供的。
- accumulator:累加器,将值累加到收集器中,相似 reduce 中的 accumulator。
- combiner:合并器,用于并行流场景,相似 reduce 中的 combiner。
- finisher:后果转换器,将汇总对象转换为最终的指定类型对象。
- characteristics:收集器特色标识,如是否反对并发等。
那用收集器实现相似下面的 avg 试试!
@Data
public class AvgCollector implements Collector<BigDecimal, SumCount, Optional<BigDecimal>> {
private int scale;
private int roundingMode;
public AvgCollector(int scale, int roundingMode) {
this.scale = scale;
this.roundingMode = roundingMode;
}
@Override
public Supplier<SumCount> supplier() {return SumCount::new;}
@Override
public BiConsumer<SumCount, BigDecimal> accumulator() {return (sumCount, bigDecimal) -> {sumCount.setSum(sumCount.getSum().add(bigDecimal));
sumCount.setCount(sumCount.getCount() + 1);
};
}
@Override
public BinaryOperator<SumCount> combiner() {return (sumCount, otherSumCount) -> {SumCount sumCountNew = new SumCount();
sumCountNew.setSum(sumCount.getSum().add(otherSumCount.getSum()));
sumCountNew.setCount(sumCount.getCount() + otherSumCount.getCount());
return sumCountNew;
};
}
@Override
public Function<SumCount, Optional<BigDecimal>> finisher() {
return sumCount -> {if (sumCount.getCount() == 0) {return Optional.empty();
}
return Optional.of(sumCount.getSum().divide(BigDecimal.valueOf(sumCount.getCount()), this.scale, this.roundingMode));
};
}
@Override
public Set<Characteristics> characteristics() {return Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.UNORDERED));
}
}
如上,实现一个 AvgCollector
收集器,而后将这个收集器传给 collect 函数即可。
List<Order> orders = getOrders();
Optional<BigDecimal>> avg = orders.stream()
.map(Order::getGoodsAmount)
.collect(new AvgCollector(2, BigDecimal.ROUND_HALF_UP));
整体执行过程如下:
能够发现,其实 Collector 相比 reduce,就是把相干操作都封装到一个收集器外面去了,这样做的益处是,能够当时定义好一些 Collector,而后应用方就能够间接拿来用了。
所以,Java 也为咱们提供了一系列罕用场景的 Collector,它们放在 Collectors 中,如下:
收集器 | 用处 |
---|---|
Collectors.toList() |
将流中元素收集为 List |
Collectors.toSet() |
将流中元素收集为 Set |
Collectors.toMap() |
将流中元素收集为 Map |
Collectors.toCollection() |
将流中元素收集为任意汇合 |
Collectors.mapping() |
元素类型转换 |
Collectors.counting() |
计数 |
Collectors.minBy() |
计算最小值 |
Collectors.maxBy() |
计算最大值 |
Collectors.summingXXX() |
求和 |
Collectors.averagingXXX() |
求平均数 |
Collectors.reducing() |
迭代操作 |
Collectors.groupingBy() |
分组汇总 |
Collectors.joining() |
拼接字符串 |
Collectors.collectingAndThen() |
收集后果后,对后果再执行一次类型转换 |
能够发现,Java 曾经为咱们提供了大量的收集器实现,对于绝大多数场景,咱们并不需要本人去实现收集器啦!
以上函数就不一一介绍了,介绍几个典型例子,如下:
元素收集到 TreeSet 中
TreeSet<Order> orderSet = orders.stream()
.collect(Collectors.toCollection(TreeSet::new));
元素收集到 Map 中
List<Order> orders = getOrders();
Map<Long, Order> orderMap = orders.stream()
.collect(Collectors.toMap(Order::getOrderId, Function.identity()));
如上,Order::getOrderId
函数为 Map 提供 Key 值,Function.identity()
函数定义如下:
它的作用是间接返回传给它的参数,你写成 o -> o
也是能够的,如果你想得到 Map<order_id, goods_amount>
这样的 Map,那应该如下写:
List<Order> orders = getOrders();
Map<Long, BigDecimal> amountMap = orders.stream()
.collect(Collectors.toMap(Order::getOrderId, Order::getGoodsAmount));
在晓得了怎么获取 Key 与 Value 后,Collectors.toMap()
收集器就晓得怎么去生成 Map 了。
但 toMap 有一个容易疏忽的坑,就是默认状况下,如果 List 生成的 Key 值有反复,则会抛出异样,如果你不想抛异样,能够再传入一个抵触处理函数,如下:
List<Order> orders = getOrders();
Map<Long, Order> orderMap = orders.stream()
.collect(Collectors.toMap(Order::getOrderId, Function.identity(), (ov, v)->v));
(ov, v)->v
函数含意是,当新元素 Key 值抵触时,ov 是 map 中的旧值,v 是新值,返回 v 则代表应用新值,即前面元素笼罩后面元素的值。
实现分组汇总操作
比方咱们常常须要将 List 分组为 Map<K, List<V>>
的模式,能够应用 groupingBy 收集器,看 groupingBy 收集器的定义,如下:
它须要提供两个参数,第一个参数 classifier 指定分类的 Key 回调函数,第二个参数 downstream 指定上游收集器,即提供每个 Key 对应 Value 的聚合收集器。
看几个例子:
按省份分组汇总订单
Map<Integer, List<Order>> groupedOrderMap = orders.stream()
.collect(Collectors.groupingBy(Order::getProvince, Collectors.toList()));
其中 Order::getProvince
函数提供分类的 Key 值,Collectors.toList()
提供分类后的 Value 聚合操作,将值聚合成 List。
按省份分组汇总单量
相似如下 SQL:
select province, count(*) from order group by province;
java 实现如下:
Map<Integer, Long> groupedCountMap = orders.stream()
.collect(Collectors.groupingBy(Order::getProvince,
Collectors.counting()));
按省份分组汇总金额
相似如下 SQL:
select province, sum(goods_amount) from order group by province;
java 实现如下:
Map<Integer, Optional<BigDecimal>> groupedAmountMap = orders.stream()
.collect(Collectors.groupingBy(Order::getProvince,
Collectors.mapping(Order::getGoodsAmount,
Collectors.reducing(BigDecimal::add))));
按省份分组汇总单号
相似如下 SQL:
select province, group_concat(order_id) from order group by province;
java 实现如下:
Map<Integer, String> groupedOrderIdMap = orders.stream()
.collect(Collectors.groupingBy(Order::getProvince,
Collectors.mapping(order -> order.getOrderId().toString(),
Collectors.joining(","))));
按省、市汇总并计算单量、金额等
相似如下 SQL:
select province, city, count(*), group_concat(order_id), group_concat(goods_amount),
sum(goods_amount), min(goods_amount), max(goods_amount), avg(goods_amount)
from order
group by province, city;
java 实现如下:
@NoArgsConstructor
@Data
class ProvinceCityStatistics {
private Integer province;
private Integer city;
private Long count;
private String orderIds;
private List<BigDecimal> amounts;
private BigDecimal sum;
private BigDecimal min;
private BigDecimal max;
private BigDecimal avg;
public ProvinceCityStatistics(Order order){this.province = order.getProvince();
this.city = order.getCity();
this.count = 1L;
this.orderIds = String.valueOf(order.getOrderId());
this.amounts = new ArrayList<>(Collections.singletonList(order.getGoodsAmount()));
this.sum = order.getGoodsAmount();
this.min = order.getGoodsAmount();
this.max = order.getGoodsAmount();
this.avg = order.getGoodsAmount();}
public ProvinceCityStatistics accumulate(ProvinceCityStatistics other) {
this.count = this.count + other.count;
this.orderIds = this.orderIds + "," + other.orderIds;
this.amounts.addAll(other.amounts);
this.sum = this.sum.add(other.sum);
this.min = this.min.compareTo(other.min) <= 0 ? this.min : other.min;
this.max = this.max.compareTo(other.max) >= 0 ? this.max : other.max;
this.avg = this.sum.divide(BigDecimal.valueOf(this.count), 2, BigDecimal.ROUND_HALF_UP);
return this;
}
}
List<Order> orders = getOrders();
Map<String, Optional<ProvinceCityStatistics>> groupedMap = orders.stream().collect(Collectors.groupingBy(order -> order.getProvince() + "," + order.getCity(),
Collectors.mapping(order -> new ProvinceCityStatistics(order),
Collectors.reducing(ProvinceCityStatistics::accumulate)))
);
groupedMap.values().stream().map(Optional::get).forEach(provinceCityStatistics -> {Integer province = provinceCityStatistics.getProvince();
Integer city = provinceCityStatistics.getCity();
long count = provinceCityStatistics.getCount();
String orderIds = provinceCityStatistics.getOrderIds();
List<BigDecimal> amounts = provinceCityStatistics.getAmounts();
BigDecimal sum = provinceCityStatistics.getSum();
BigDecimal min = provinceCityStatistics.getMin();
BigDecimal max = provinceCityStatistics.getMax();
BigDecimal avg = provinceCityStatistics.getAvg();
System.out.printf("province:%d, city: %d -> count: %d, orderIds: %s, amounts: %s," +
"sum: %s, min: %s, max: %s, avg : %s %n",
province, city, count, orderIds, amounts, sum, min, max, avg);
});
执行后果如下:
能够发现,应用 Collectors.reducing
能够实现性能,但有点繁琐,且代码含意不显著,因而我封装了一个 MultiCollector 收集器,用来将多种收集器组合起来,实现这种简单场景,如下:
/**
* 将多个收集器,组合成一个收集器
* 汇总后果保留在 Map<String, Object> 中,最终后果转换成 R 类型返回
*
* @param <T>
*/
public class MultiCollector<T, R> implements Collector<T, Map<String, Object>, R> {
private Class<R> clazz;
private Map<String, Collector<T, ?, ?>> collectorMap;
public MultiCollector(Class<R> clazz, Map<String, Collector<T, ?, ?>> collectorMap) {
this.clazz = clazz;
this.collectorMap = collectorMap;
}
@Override
public Supplier<Map<String, Object>> supplier() {Map<String, Supplier<?>> supplierMap = new HashMap<>();
collectorMap.forEach((fieldName, collector) -> supplierMap.put(fieldName, collector.supplier()));
return () -> {Map<String, Object> map = new HashMap<>();
supplierMap.forEach((fieldName, supplier) -> {map.put(fieldName, supplier.get());
});
return map;
};
}
@Override
@SuppressWarnings("all")
public BiConsumer<Map<String, Object>, T> accumulator() {Map<String, BiConsumer<?, T>> accumulatorMap = new HashMap<>();
collectorMap.forEach((fieldName, collector) -> accumulatorMap.put(fieldName, collector.accumulator()));
return (map, order) -> {accumulatorMap.forEach((fieldName, accumulator) -> {((BiConsumer)accumulator).accept(map.get(fieldName), order);
});
};
}
@Override
@SuppressWarnings("all")
public BinaryOperator<Map<String, Object>> combiner() {Map<String, BinaryOperator<?>> combinerMap = new HashMap<>();
collectorMap.forEach((fieldName, collector) -> combinerMap.put(fieldName, collector.combiner()));
return (map, otherMap) -> {combinerMap.forEach((fieldName, combiner) -> {map.put(fieldName, ((BinaryOperator)combiner).apply(map.get(fieldName), otherMap.get(fieldName)));
});
return map;
};
}
@Override
@SuppressWarnings("all")
public Function<Map<String, Object>, R> finisher() {Map<String, Function<?, ?>> finisherMap = new HashMap<>();
collectorMap.forEach((fieldName, collector) -> finisherMap.put(fieldName, collector.finisher()));
// 将 Map<String, Object> 反射转换成指定类对象,这里用 json 反序列化也能够
return map -> {R result = newInstance(clazz);
finisherMap.forEach((fieldName, finisher) -> {Object value = ((Function)finisher).apply(map.get(fieldName));
setFieldValue(result, fieldName, value);
});
return result;
};
}
@Override
public Set<Characteristics> characteristics() {return Collections.emptySet();
}
private static <R> R newInstance(Class<R> clazz){
try {return clazz.newInstance();
} catch (ReflectiveOperationException e) {return ExceptionUtils.rethrow(e);
}
}
@SuppressWarnings("all")
private static void setFieldValue(Object obj, String fieldName, Object value){if (obj instanceof Map){((Map)obj).put(fieldName, value);
} else {
try {new PropertyDescriptor(fieldName, obj.getClass()).getWriteMethod().invoke(obj, value);
} catch (Exception e) {ExceptionUtils.rethrow(e);
}
}
}
}
而后封装一些语义更加明确的通用 Collector 办法,如下:
public class CollectorUtils {
/**
* 取第一个元素,相似 Stream.findFirst,返回 Optional<U>
* @param mapper 获取字段值的函数
* @return
*/
public static <T,U> Collector<T, ?, Optional<U>> findFirst(Function<T, U> mapper){return Collectors.mapping(mapper, Collectors.reducing((u1, u2) -> u1));
}
/**
* 取第一个元素,相似 Stream.findFirst,返回 U,可能是 null
* @param mapper 获取字段值的函数
* @return
*/
public static <T,U> Collector<T, ?, U> findFirstNullable(Function<T, U> mapper){
return Collectors.mapping(mapper,
Collectors.collectingAndThen(Collectors.reducing((u1, u2) -> u1), opt -> opt.orElse(null)));
}
/**
* 收集指定字段值为 List
* @param mapper 获取字段值的函数
* @return
*/
public static <T,U> Collector<T, ?, List<U>> toList(Function<T, U> mapper){return Collectors.mapping(mapper, Collectors.toList());
}
/**
* 收集指定字段为逗号分隔的字符串
* @param mapper 获取字段值的函数
* @return
*/
public static <T, U> Collector<T, ?, String> joining(Function<T, U> mapper, CharSequence delimiter){return Collectors.mapping(mapper.andThen(o -> Objects.toString(o, "")), Collectors.joining(delimiter));
}
/**
* 对 BigDecimal 求和,返回 Optional<BigDecimal> 类型汇总值
* @param mapper 获取字段值的函数
* @return
*/
public static <T> Collector<T, ?, Optional<BigDecimal>> summingBigDecimal(Function<T, BigDecimal> mapper){return Collectors.mapping(mapper, Collectors.reducing(BigDecimal::add));
}
/**
* 对 BigDecimal 求和,返回 BigDecimal 类型汇总值,可能是 null
* @param mapper 获取字段值的函数
* @return
*/
public static <T> Collector<T, ?, BigDecimal> summingBigDecimalNullable(Function<T, BigDecimal> mapper){
return Collectors.mapping(mapper,
Collectors.collectingAndThen(Collectors.reducing(BigDecimal::add), opt -> opt.orElse(null)));
}
/**
* 对 BigDecimal 求平均值,返回 Optional<BigDecimal> 类型平均值
* @param mapper 获取字段值的函数
* @return
*/
public static <T> Collector<T, ?, Optional<BigDecimal>> averagingBigDecimal(Function<T, BigDecimal> mapper, int scale, int roundingMode){return Collectors.mapping(mapper, new AvgCollector(scale, roundingMode));
}
/**
* 对 BigDecimal 求平均值,返回 BigDecimal 类型平均值,可能是 null
* @param mapper 获取字段值的函数
* @return
*/
public static <T> Collector<T, ?, BigDecimal> averagingBigDecimalNullable(Function<T, BigDecimal> mapper, int scale, int roundingMode){
return Collectors.mapping(mapper,
Collectors.collectingAndThen(new AvgCollector(scale, roundingMode), opt -> opt.orElse(null)));
}
/**
* 求最小值,返回最小值 Optional<U>
* @param mapper 获取字段值的函数
* @return
*/
public static <T,U extends Comparable<? super U>> Collector<T, ?, Optional<U>> minBy(Function<T, U> mapper){return Collectors.mapping(mapper, Collectors.minBy(Comparator.comparing(Function.identity())));
}
/**
* 求最小值,返回最小值 U,可能是 null
* @param mapper 获取字段值的函数
* @return
*/
public static <T,U extends Comparable<? super U>> Collector<T, ?, U> minByNullable(Function<T, U> mapper){
return Collectors.collectingAndThen(
Collectors.mapping(mapper,
Collectors.minBy(Comparator.comparing(Function.identity()))), opt -> opt.orElse(null));
}
/**
* 求最大值,返回最大值 Optional<U>
* @param mapper 获取字段值的函数
* @return
*/
public static <T,U extends Comparable<? super U>> Collector<T, ?, Optional<U>> maxBy(Function<T, U> mapper){return Collectors.mapping(mapper, Collectors.maxBy(Comparator.comparing(Function.identity())));
}
/**
* 求最大值,返回最大值 U,可能是 null
* @param mapper 获取字段值的函数
* @return
*/
public static <T,U extends Comparable<? super U>> Collector<T, ?, U> maxByNullable(Function<T, U> mapper){
return Collectors.collectingAndThen(
Collectors.mapping(mapper,
Collectors.maxBy(Comparator.comparing(Function.identity()))), opt -> opt.orElse(null));
}
}
CollectorUtils 中封装的各 Collector 用处如下:
办法 | 用处 |
---|---|
findFirst(mapper) |
获取第一个值,相似 Stream.findFirst,返回 Optional |
findFirstlNullable(mapper) |
获取第一个值,相似 Stream.findFirst,返回值可能是 null |
toList(mapper) |
用于实现对指定字段收集为 List |
joining(mapper) |
实现相似 group_concat(order_id)的性能 |
summingBigDecimal(mapper) |
用于对 BigDecimal 做汇总解决,返回Optional<BigDecimal> |
summingBigDecimalNullable(mapper) |
用于对 BigDecimal 做汇总解决,返回 BigDecimal |
averagingBigDecimal(mapper) |
实现对 BigDecimal 求平均数,返回Optional<BigDecimal> |
averagingBigDecimal(mapper) |
实现对 BigDecimal 求平均数,返回 BigDecimal |
minBy(mapper) |
实现求最小值,返回Optional<BigDecimal> |
minByNullable(mapper) |
实现求最小值,返回 BigDecimal |
maxBy(mapper) |
实现求最大值,返回Optional<BigDecimal> |
maxByNullable(mapper) |
实现求最大值,返回 BigDecimal |
而后联合 MultiCollector 收集器与 CollectorUtils 中的各种 Collector,就能够实现各种简单的分组汇总逻辑了,如下:
@NoArgsConstructor
@Data
class ProvinceCityStatistics {
private Integer province;
private Integer city;
private Long count;
private String orderIds;
private List<BigDecimal> amounts;
private BigDecimal sum;
private BigDecimal min;
private BigDecimal max;
private BigDecimal avg;
}
List<Order> orders = getOrders();
Map<String, ProvinceCityStatistics> groupedMap = orders.stream().collect(Collectors.groupingBy(order -> order.getProvince() + "," + order.getCity(),
new MultiCollector<>(
ProvinceCityStatistics.class,
// 指定 ProvinceCityStatistics 各字段对应的收集器
MapBuilder.<String, Collector<Order, ?, ?>>create()
.put("province", CollectorUtils.findFirstNullable(Order::getProvince))
.put("city", CollectorUtils.findFirstNullable(Order::getCity))
.put("count", Collectors.counting())
.put("orderIds", CollectorUtils.joining(Order::getOrderId, ","))
.put("amounts", CollectorUtils.toList(Order::getGoodsAmount))
.put("sum", CollectorUtils.summingBigDecimalNullable(Order::getGoodsAmount))
.put("min", CollectorUtils.minByNullable(Order::getGoodsAmount))
.put("max", CollectorUtils.maxByNullable(Order::getGoodsAmount))
.put("avg", CollectorUtils.averagingBigDecimalNullable(Order::getGoodsAmount, 2, BigDecimal.ROUND_HALF_UP))
.build())
)
);
groupedMap.forEach((key, provinceCityStatistics) -> {Integer province = provinceCityStatistics.getProvince();
Integer city = provinceCityStatistics.getCity();
long count = provinceCityStatistics.getCount();
String orderIds = provinceCityStatistics.getOrderIds();
List<BigDecimal> amounts = provinceCityStatistics.getAmounts();
BigDecimal sum = provinceCityStatistics.getSum();
BigDecimal min = provinceCityStatistics.getMin();
BigDecimal max = provinceCityStatistics.getMax();
BigDecimal avg = provinceCityStatistics.getAvg();
System.out.printf("province:%d, city: %d -> count: %d, orderIds: %s, amounts: %s," +
"sum: %s, min: %s, max: %s, avg : %s %n",
province, city, count, orderIds, amounts, sum, min, max, avg);
});
执行后果如下:
我想如果搞懂了这个,Collector API 简直就全玩明确了😅
总结
Stream API 十分实用,它的设计相似于 SQL,相比于间接遍历解决汇合的实现代码,用它来实现的可读性会更强。
当然,好用也不要滥用,API 应用场景应该与其具体用意绝对应,比方不要在 filter 外面去写非过滤逻辑的代码,尽管代码可能跑起来没问题,但这会误导读者,反而起到负面作用。