写点什么

我有一篇 Java Stream 使用手册,学了就是你的了!

  • 2023-03-23
    湖南
  • 本文字数:12752 字

    阅读完需:约 42 分钟

日常编程工作中,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 里面的函数大多很简单,就不逐一介绍了,如下:

这些是 Stream 比较基础的用法,下面看看一些更高级的用法吧!

reduce 函数

可以看到 Stream 提供了 min、max 操作,但并没有提供 sum、avg 这样的操作,如果要实现 sum、avg 操作,就可以使用 reduce(迭代)函数来实现,reduce 函数有 3 个,如下:


下面以订单金额的 sum 汇总操作为示例,如下:


  1. 带初始值与累加器的 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 函数的执行逻辑大致如下:

  1. 无初始值的 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。


  1. 带初始值、累加器、合并器的 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 函数还提供了一个合并器,它是做什么用的?


其实合并器用于并行流场景,当使用多个线程处理数据时,数据拆分给多个线程后,每个线程使用累加器计算出自己的汇总值,然后使用合并器将各个线程的汇总值再次汇总,从而计算出最后结果,执行过程如下图:

  1. 使用 reduce 实现 avg


reduce 可以实现 avg,但稍微有点繁琐,如下:

@Dataprivate 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 试试!

@Datapublic 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 中,如下:

以上函数就不一一介绍了,介绍几个典型例子,如下:


  1. 元素收集到 TreeSet 中

TreeSet<Order> orderSet = orders.stream()        .collect(Collectors.toCollection(TreeSet::new));
复制代码
  1. 元素收集到 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 则代表使用新值,即后面元素覆盖前面元素的值。


  1. 实现分组汇总操作

比如我们经常需要将 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@Dataclass 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 用途如下:

然后结合 MultiCollector 收集器与 CollectorUtils 中的各种 Collector,就可以实现各种复杂的分组汇总逻辑了,如下:

@NoArgsConstructor@Dataclass 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 里面去写非过滤逻辑的代码,虽然代码可能跑起来没问题,但这会误导读者,反而起到负面作用。


作者:扣钉日记

链接:https://juejin.cn/post/7203880677195087933

来源:稀土掘金

用户头像

还未添加个人签名 2021-07-28 加入

公众号:该用户快成仙了

评论

发布
暂无评论
我有一篇Java Stream使用手册,学了就是你的了!_做梦都在改BUG_InfoQ写作社区