java8实战:使⽤流收集数据之toList、joining、groupBy(多字段分组)java8专栏⽬录:
1.
2.
3.
4.
5.
本⽂将从Collectos中构建收集器⼊⼿,详细介绍java8提供了哪些收集器,重点介绍:toList、toSet、toCollection、joining、groupBy(包含多级分组)、reducing的核⼼实现原理与使⽤⽰例。
本节⽬录
1、toList、toSet、toCollection
⾸先对流中的数据进⾏计算,最终返回的数据类型为集合。Collectors中定义了如下3集合类收集器,其声明如下:
public static<T> Collector<T,?, List<T>>toList()
public static<T> Collector<T,?, Set<T>>toSet()
public static<T, C extends Collection<T>> Collector<T,?, C>toCollection(Supplier<C> collectionFactory)
温馨提⽰:建议根据上篇的理论,再来反推⼀下这些Collector中的核⼼属性的值,例如supplier、accumulator、combiner、characteristics。不过特别注意,toList、toCollection是不⽀持并⾏运⾏的,但toSet()⽅法⽀持并⾏运⾏。
我们⾸先来看⼀个⼀直使⽤的⽰例,返回菜单中所有菜品的名称:
public static void test_toList(List<Dish> menu){
List<String> names = menu.stream().map(Dish::getName)
.List());
}
由于toList⽅法的实现原理已经在 中也详细介绍,故本篇不再重点介绍。
2、joining
Collectors定义了如下3个重载⽅法。
public static Collector<CharSequence,?, String>joining()
public static Collector<CharSequence,?, String>joining(CharSequence delimiter)
public static Collector<CharSequence,?, String>joining(CharSequence delimiter,
CharSequence prefix, CharSequence suffix)
2.1 joining
public static Collector<CharSequence,?, String>joining(){
return new CollectorImpl<CharSequence, StringBuilder, String>(
StringBuilder::new, StringBuilder::append,
(r1, r2)->{ r1.append(r2);return r1;},
StringBuilder::toString, CH_NOID);
}
Supplier< A> supplier()
其函数为StringBuilder::new,即通过该⽅法创建⼀个StringBuilder⽅法,作为累积器的初始值。
BiConsumer<A, T> accumulator
累积器:StringBuilder::append,即会对流中的元素执⾏追加。
BinaryOperator< A> combiner
组合器,也是调⽤append⽅法,进⾏字符串的规约。
Function<A,R> finisher
转换器:由于累积器返回的最终对象为StringBuilder,并不是⽬标String类型,故需要调⽤StringBuilder#toString⽅法进⾏转换Set< Characteristics> characteristics
⽆任何⾏为。
从上⾯的函数定义我们可以得出该⽅法的作⽤:针对字符串流,会对流中的元素执⾏字符的追加动作,流元素之间没有分隔符号,⽰例如下:
2.2 joining(CharSequence delimiter)
public static Collector<CharSequence,?, String>joining(CharSequence delimiter){
return joining(delimiter,"","");
}
public static Collector<CharSequence,?, String>joining(CharSequence delimiter,
CharSequence prefix,
CharSequence suffix){
return new CollectorImpl<>(
()->new StringJoiner(delimiter, prefix, suffix),
StringJoiner::add, StringJoiner::merge,
StringJoiner::toString, CH_NOID);
}
Supplier< A> supplier()
其函数为() -> new StringJoiner(delimiter, prefix, suffix),累积器的初始值为StringJoiner。
BiConsumer<A, T> accumulator
累积器:StringJoiner::append,即会对流中的元素执⾏追加。
BinaryOperator< A> combiner
组合器,StringJoiner::merge。
Function<A,R> finisher
转换器:由于累积器返回的最终对象为StringBuilder,并不是⽬标String类型,故需要调⽤StringBuilder#toString⽅法进⾏转换Set< Characteristics> characteristics
⽆任何⾏为。
其⽰例如下:
3、聚合相关收集器
聚合相关收集器,主要包括minBy、maxBy、sum、avg等相关函数,其主要⽅法声明如下:
public static<T> Collector<T,?, Optional<T>>minBy(Comparator<?super T> comparator)
public static<T> Collector<T,?, Optional<T>>maxBy(Comparator<?super T> comparator)
public static<T> Collector<T,?, Integer>summingInt(ToIntFunction<?super T> mapper)
public static<T> Collector<T,?, Long>summingLong(ToLongFunction<?super T> mapper)
public static<T> Collector<T,?, Double>summingDouble(ToDoubleFunction<?super T> mapper) public static<T> Collector<T,?, Double>averagingInt(ToIntFunction<?super T> mapper)
public static<T> Collector<T,?, Double>averagingLong(ToLongFunction<?super T> mapper)
public static<T> Collector<T,?, Double>averagingDouble(ToDoubleFunction<?super T> mapper)
上⾯这些⽅法⽐较简单,下⾯举个简单的例⼦介绍其使⽤:
4 分组
Collectors提供了3个groupingBy重载⽅法,我们⼀个⼀个来理解。
4.1 从⽰例⼊⼿
我们从其中⼀个最简单的函数说起,从⽽慢慢引出
public static<T, K> Collector<T,?, Map<K, List<T>>>groupingBy(
Function<?super T,?extends K> classifier)
Collector<T, ?, Map<K, List< T>>>
⾸先我们先来关注该⽅法的返回值Collector<T, ?, Map<K,List< T>>,其最终返回的数据类型为:Map<K, List< T>>
Function<? super T, ? extends K> classifier
分类函数。
⽰例如下:例如如下是购物车实体类,并且初始化数据如下:
public class ShopCar {
private int id;
private int sellerId;
private String sellerName;
private String goodsName;
private int buyerId;
private String buyerName;
private int num;
}
// 初始化数据如下:
public static List<ShopCar>initShopCar(){
return Arrays.asList(
new ShopCar(1,1,"天猫","华为⼿机",1,"dingw",5),
new ShopCar(1,2,"京东","华为⼿机",2,"ly",2),
new ShopCar(1,1,"京东","⼩⽶⼿机",3,"zhl",3),
new ShopCar(1,2,"1号店","华为⼿机",1,"dingw",5),
groupby是什么函数new ShopCar(1,2,"天猫","苹果⼿机",1,"dingw",2)
);
}
⾸先我们看⼀下java8之前的写法:
public static void test_group_jdk7(List<ShopCar> shopCars){
Map<String, List<ShopCar>> shopBySellerNameMap =new HashMap<>();
for(ShopCar c : shopCars ){
ainsKey( c.getSellerName())){
<(c.getSellerName()).add(c);
}else{
List<ShopCar> aList =new ArrayList<>();
shopBySellerNameMap.SellerName(), aList);
aList.add(c);
}
}
print(shopBySellerNameMap);
}
上⾯的代码应该很容易理解,根据商家名称进⾏分组,拥有相同商家的名称的购物车项组成⼀个集合,最终返回Map<String, List< ShopCar >>类型的数据。
那如何使⽤java8的流分组特性来编写对应的代码呢?下⾯的思考过程⾮常关键,经过前⾯的学习,我想⼤家应该也具备了如下分析与编写的能⼒?
⾸先其声明如下:public static <T, K> Collector<T, ?, Map<K, List< T>>> groupingBy(Function<? super T, ? extends K> classifier),那在本例中,T,K这两个参数代表什么意思呢?
T : ShopCar
K : String (sellerName的类型)
其判断的主要依据为groupingBy⽅法返回的参数Collector<T, ?, Map<K, List< T>>>,代表<T, A, R>,其中最后⼀个泛型参数R对应的就是本例需要返回的Map<K, List< T>>,故分析出T,K代表的含义。
然后再看其参数:Function<? super T, ? extends K> classifier,即接受的函数式编程接⼝为T -> K,即通过ShopCar 返回⼀个String,⼜根据其名称可知,该函数为⼀个分类函数,故基本可以写成如下代码:
public static void test_group_jdk8(List<ShopCar> shopCars){
Map<String, List<ShopCar>> shopBySellerNameMap =
shopCars
.stream()
.upingBy(ShopCar::getSellerName));
//.upingBy( (ShopCar c) -> c.getSellerName() ))
print(shopBySellerNameMap);
}
其运⾏效果如下:
为了加深对groupingBy⽅法的理解,接下来我们重点分析⼀下其源码的实现。
4.2 源码分析groupingBy⽅法
public static<T, K> Collector<T,?, Map<K, List<T>>>groupingBy(Function<?super T,?extends K> classifier){// @1
return groupingBy(classifier,toList());// @2
}
代码@1:分类参数,已经在上⽂中详细介绍。
代码@2:调⽤groupingBy重载⽅法,传⼊的参数为toList(),有点意思,传⼊的参数为List(),结合上⽂中的⽰例,需要返回值类型为:Map<String, List< ShopCar>>,与这⾥的List对应起来了。
public static<T, K, A, D> Collector<T,?, Map<K, D>>groupingBy(Function<?super T,?extends K> classifier, Collector<?super T, A, D> downstream){ return groupingBy(classifier, HashMap::new, downstream);
}
该重载⽅法,再次调⽤3个参数的groupingBy⽅法,其中第⼆个参数为HashMap::new,即创建⼀个Map对象,我们重点关注3个参数的groupingBy。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论