java8stream 统计、汇总、多字段分组、多个列汇总统计
⽂章⽬录
前⾔
本⽂将展⽰groupingBy收集器的多个⽰例,阅读本⽂需要先准备Java 和Java收集器Collector的知识。
⼀、GroupingBy 收集器
Java8的Stream API允许我们以声明的⽅式来处理数据。
静态⼯⼚⽅法:upingBy(),以及upingByConcunrrent(),给我们提供了类似SQL语句中的"GROUP BY"的功能。这两个⽅法将数据按某些属性分组,并存储在Map中返回。
作为collect⽅法的参数,Collector是⼀个接⼝,它是⼀个可变的汇聚操作,将输⼊元素累计到⼀个可变的结果容器中;它会在所有元素都处理完毕后,将累积的结果转换为⼀个最终的表⽰(这是⼀个可选操作);
Collectors本⾝提供了关于Collector的常见汇聚实现,Collectors的内部类CollectorImpl实现了Collector接⼝,Collectors本⾝实际上是⼀个⼯⼚。
Collector 类⽅法:Collector主要定义了容器的类型,添加元素的⽅法,容器合并的⽅法还有输出的结果。supplier就是⽣成容器accumulator是添加元素combiner是合并容器finisher是输出的结果
characteristics是定义容器的三个属性(三个枚举值),包括是否有明确的finisher,是否需要同步,是否有序。
1. CONCURRENT(集合的操作需要同步):表⽰中间结果只有⼀个,即使在并⾏流的情况下。所以只有在并⾏流且收集器不具备CONCURRENT特性时,combiner⽅法返回的lambda表达式才会执⾏(中间结果容器只有⼀个就⽆需合并)
2. UNORDER(集合是⽆序的):表⽰流中的元素⽆序。
3. IDENTITY_FINISH(不⽤finisher):表⽰中间结果容器类型与最终结果类型⼀致,此时finiser⽅法不会被调⽤
其中这⾥的泛型所表⽰的含义是:
T:表⽰流中每个元素的类型。
A:表⽰中间结果容器的类型。
R:表⽰最终返回的结果类型。下⾯是⼏个重载的groupnigBy⽅法:
参数 :分类函数public interface Collector<T, A, R> {    Supplier<A> supplier();    BiConsumer<A, T> accumulator();    BinaryOperator<A> combiner();    Function<A, R> finisher();    Set<Characteristics> characteristics();}
1
2
3
4
5
6
7
8
参数:分类函数,第⼆个收集器
参数:分类函数,供应者⽅法(提供作为返回值的Map的实现),第⼆个收集器
⼆、使⽤⽰例
2.1 准备
先定义⼀个BlogPost类:
BlogPostType:static <T,K> Collector<T,?,Map<K,List<T>>>groupingBy(Function<? super T,? extends K> classifier)
1
2static <T,K,A,D> Collector<T,?,Map<K,D>>groupingBy(Function<? super T,? extends K> classifier,Collector<? super T,A,D> downstream)
1
2
3static <T,K,D,A,M extends Map<K,D>> Collector<T,?,M>groupingBy(Function<? super T,? extends K> classifier,Supplier<M> mapFactory, Collector<? super T,A,D> downstream)
1
2
3class BlogPost {String title;String author;BlogPostType type;int likes;}
1
2
3
4
56enum BlogPostType {NEWS,REVIEW,GUIDE }
1
2
3
4
5
6
BlogPost列表:
2.2 根据单⼀字段分组
最简单的groupingBy⽅法,只有⼀个分类函数做参数。分类函数作⽤于strema⾥⾯的每个元素。分类函数处理后返回的每个元素作为返回Map的key。
根据博客⽂章类型来分组:
groupingBy(BlogPost::getType) 默认返回的是toList。
2.3 根据Map 的key 的类型分组
分类函数并没有限制返回字符串或标量值。返回map的key可以是任何对象。只要实现了其equals和hashcode⽅法。
下⾯⽰例根据type和author组合⽽成的BlogPost实例来分组:
2.4 修改返回Map 的value 的类型
groupingBy的第⼆个重载⽅法有⼀个额外的collector参数(downstream),此参数作⽤于第⼀个collector产⽣的结果。
如果只⽤⼀个分类函数做参数,那么默认会使⽤toList()这个collector来转换结果。
下⾯的代码显⽰地使⽤了toSet()这个collector传递给downstream这个参数,因此会得到⼀个博客⽂章的Set。
2.5 修改返回⾃定义类型public class Tuple {    String author;    BlogPostType type;    public String getAuthor() {        return author;    }    public void setAuthor(String author) {        this.author = author;    }    public BlogPostType getType() {        return type;    }    public void setType(BlogPostType type) {        pe = type;    }}
1
2
34
java stream5
6
7
8
9
10
11
1213
14
15
16
17
18
19
20
21List<BlogPost> posts = Arrays.asList( ... );
1Map<BlogPostType, List<BlogPost>> postsPerType = posts.stream().collect(groupingBy(BlogPost::getType));
1
2 Map<BlogPost, List<BlogPost>> postsPerTypeAndAuthor = posts.stream()                .collect(groupingBy(post -> new BlogPost()));
1
2Map<BlogPostType, Set<BlogPost>> postsPerType = posts.stream().collect(groupingBy(BlogPost::getType, toSet()));
1
2
mapping函数:mapper:返回参数对象,downstream收集的集合值。
⽰例代码:
mapping() 收集器,⾃定义返回。
2.6 根据多个字段分组
downstream参数的另外⼀个⽤处就是基于分组结果,做第⼆次分组。
下⾯代码,⾸先根据author分组,然后再根据type分组:
2.7 得到分组结果的平均值
通过使⽤downstream,我们可以把集合函数应⽤到第⼀次分组的结果上。⽐如,获取到每种类型博客的被喜欢次数(likes)的平均值:
2.8 得到分组结果的总计
计算每种类型被喜欢次数的总数:
2.9 得到分组结果中的最⼤或最⼩值
我们还可以得到每种类型博客被喜欢次数最多的是多少:
类似的,可以⽤minxBy得到每种类型博客中被喜欢次数最少的次数是多少。Collector<T, ?, R> mapping(Function<? super T, ? extends U> mapper,                              Collector<? super U, A, R> downstream)
1
2Map<BlogPostType, List<Map<String, Object>>> postsPerType = posts.stream()                .collect(groupingBy(BlogPost::getType, mapping(n->getBlogPost(n), toList())));                privatestatic Map<String, Object> getBlogPost(BlogPost blogPost) {        Map<String, Object> map = new HashMap<>();        map.put("title", Title());        return map;    }
1
2
3
4
5
6
7
8Map<String, Map<BlogPostType, List>> map = posts.stream().collect(groupingBy(BlogPost::getAuthor, groupingBy(BlogPost::getType)));
1
2Map<BlogPostType, Double> averageLikesPerType = posts.stream().collect(groupingBy(BlogPost::getType, averagingInt(BlogPost::getLikes)));
1
2
3
4
5Map<BlogPostType, Integer> likesPerType = posts.stream().collect(groupingBy(BlogPost::getType, summingInt(BlogPost::getLikes)));
12
3
4
5Map<BlogPostType, Optional<BlogPost>> maxLikesPerPostType = posts.stream().collect(groupingBy(BlogPost::getType,maxBy(comparingInt(BlogPost::getLikes))));
1
2
3
4
5
6
7
注意:maxBy和minBy都考虑了当第⼀次分组得到的结果是空的场景,因此其返回结果(Map的value)是Optional<BlogPost>。
2.10 得到分组结果中某个属性的统计
Collectors API提供了⼀个统计collector,可以⽤来同时计算数量、总计、最⼩值、最⼤值、平均值等。
下⾯来统计⼀下不同类型博客的被喜欢(likes)这个属性:
返回Map中的value,IntSummaryStatistics对象,包括了每个BlogPostType的⽂章次数、被喜欢总计、平均值、最⼤值、最⼩值。
2.11 把分组结果映射为另外的类型
更复杂的聚合操作可以通过应⽤⼀个映射downstream收集器到分类函数结果上来实现。
下⾯代码讲每类博客类型的标题连接起来了。
上⾯的代码,讲每个BlogPost实例映射为了其对应的标题,然后把博客标题的stream连接成了成了字符串,形如“Post titles:[标题1,标题2,标题3]”。
2.12 修改返回Map 的类型
使⽤groupingBy的时候,如果我们要指定返回Map的具体类型,可以⽤第三个重载⽅法。通过传⼊⼀个Map供应者函数。
下⾯代码传⼊了⼀个EnumMap供应者函数,得到返回Map为EnumMap类型。
2.13 collectingAndThen 包裹⼀个收集器,对其结果应⽤转换函数
public static <T,A,R,RR> Collector<T,A,RR> collectingAndThen(Collector<T,A,R> downstream,
Function<R,RR> finisher)
⽰例代码
//对分组进⾏转换,对分组内元素进⾏计算Map<BlogPostType, IntSummaryStatistics> likeStatisticsPerType = posts.stream().collect(groupingBy(BlogPost::getType,summarizingInt(BlogPost::getLikes)));
1
2
3
4
5
6
7Map<BlogPostType, String> postsPerType = posts.stream().collect(groupingBy(BlogPost::getType,mapping(BlogPost::getTitle, joining(", ", "Post titles: [", "]"))));
1
2
3
4
5
6
7EnumMap<BlogPostType, List<BlogPost>> postsPerType = posts.stream().collect(groupingBy(BlogPost::getType,() -> new EnumMap<>(BlogPostType.class), toList()));
1
2
3
4
5
6
7

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。