关于java:我有一篇Java-Stream使用手册学了就是你的了

39次阅读

共计 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 外面去写非过滤逻辑的代码,尽管代码可能跑起来没问题,但这会误导读者,反而起到负面作用。

正文完
 0