玩转Java8的Stream之Collectors收集器
之前的⽂章中也提到了,Stream 的核⼼在于Collectors,即对处理后的数据进⾏收集。Collectors 提供了⾮常多且强⼤的API,可以将最终的数据收集成List、Set、Map,甚⾄是更复杂的结构(这三者的嵌套组合)。
Collectors 提供了很多API,有很多都是⼀些函数的重载,这⾥我个⼈将其分为三⼤类,如下:
数据收集:set、map、list
聚合归约:统计、求和、最值、平均、字符串拼接、规约
前后处理:分区、分组、⾃定义操作
API 使⽤
这⾥会讲到⼀些常⽤API 的⽤法,不会讲解所有API,因为真的是太多了,⽽且各种API的组合操作起来太可怕太复杂了。
数据收集
Collection() 将数据转成Collection,只要是Collection 的实现都可以,例如ArrayList、HashSet ,该⽅法接受⼀个Collection 的实现对象或者说Collection ⼯⼚的⼊参。
⽰例:
//List
Stream.of(1,2,3,4,5,6,8,9,0)
.Collection(ArrayList::new));
//Set
Stream.of(1,2,3,4,5,6,8,9,0)
.Collection(HashSet::new));
List()和Set() 其实和Collection() 差不多,只是指定了容器的类型,默认使⽤ArrayList 和HashSet。本来我以为这两个⽅法的内部会使⽤到Collection(),结果并不是,⽽是在内部new了⼀个CollectorImpl。
预期:
public static <T>
Collector<T, ?, List<T>> toList() {
return toCollection(ArrayList::new);
}
public static <T>
Collector<T, ?, Set<T>> toSet() {
return new toCollection(HashSet::new);
}
刚开始真是不知道作者是怎么想的,后来发现CollectorImpl 是需要⼀个Set<Collector.Characteristics>(特征集合)的东西,由于Set 是⽆序的,在toSet()⽅法中的实现传⼊了CH_UNORDERED_ID,但是toCollection()⽅法默都是CH_ID,难道是说在使⽤toCollecion()⽅法时不建议传⼊Set类型?如果有⼈
知道的话,⿇烦你告诉我⼀下。
⽰例:
//List
Stream.of(1,2,3,4,5,6,8,9,0)
.List());
//Set
Stream.of(1,2,3,4,5,6,8,9,0)
.Set());
⽰例:这⾥以Student 这个结构为例,Student 包含 id、name。
public class Student{
//唯⼀
private String id;
private String name;
public Student() {
}
public Student(String id, String name) {
this.id = id;
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
说明:这⾥制定k 为 id,value 既可以是对象本⾝,也可以指定对象的某个字段。可见,map的收集⾃定义性⾮常⾼。
Student studentA = new Student("20190001","⼩明");
Student studentB = new Student("20190002","⼩红");
Student studentC = new Student("20190003","⼩丁");
//Function.identity() 获取这个对象本⾝,那么结果就是Map<String,Student> 即 id->student
//串⾏收集
Stream.of(studentA,studentB,studentC)
.Map(Student::getId,Function.identity()));
//并发收集
Stream.of(studentA,studentB,studentC)
.parallel()
.ConcurrentMap(Student::getId,Function.identity()));
//================================================================================
//Map<String,String> 即 id->name
//串⾏收集
Stream.of(studentA,studentB,studentC)
.Map(Student::getId,Student::getName));
//并发收集
Stream.of(studentA,studentB,studentC)
.parallel()
.
ConcurrentMap(Student::getId,Student::getName));
那么如果key重复的该怎么处理?这⾥我们假设有两个id相同Student,如果他们id相同,在转成Map的时候,取name⼤⼀个,⼩的将会被丢弃。
//Map<String,Student>
Stream.of(studentA, studentB, studentC)
.collect(Collectors
.toMap(Student::getId,
Function.identity(),
BinaryOperator
.maxBy(Comparatorparing(Student::getName))));
//可能上⾯⽐较复杂,这编写⼀个命令式
//Map<String,Student>
Stream.of(studentA, studentB, studentC)
.collect(Collectors
.toMap(Student::getId,
Function.identity(),
(s1, s2) -> {
//这⾥使⽤compareTo ⽅法 s1>s2 会返回1,s1==s2 返回0 ,否则返回-1
if (((Student) s1).namepareTo(((Student) s2).name) < -1) {
return s2;
} else {
return s1;
}
}
));
如果不想使⽤默认的HashMap 或者 ConcurrentHashMap , 第三个重载⽅法还可以使⽤⾃定义的Map对象(Map⼯⼚)。
//⾃定义LinkedHashMap
//Map<String,Student>
Stream.of(studentA, studentB, studentC)
.collect(Collectors
.toMap(Student::getId,
Function.identity(),
BinaryOperator
.maxBy(Comparatorparing(Student::getName)),
LinkedHashMap::new));
聚合归约
Collectors.joining(),拼接,有三个重载⽅法,底层实现是StringBuilder,通过append⽅法拼接到⼀起,并且可以⾃定义分隔符(这个感觉还是很有⽤的,很多时候需要把⼀个list转成⼀个String,指定分隔符就可以实现了,⾮常⽅便)、前缀、后缀。
⽰例:
Student studentA = new Student("20190001", "⼩明");
Student studentB = new Student("20190002", "⼩红");
Student studentC = new Student("20190003", "⼩丁");
//使⽤分隔符:201900012019000220190003
Stream.of(studentA, studentB, studentC)
.map(Student::getId)
.collect(Collectors.joining());
//使⽤^_^ 作为分隔符
//20190001^_^20190002^_^20190003
Stream.of(studentA, studentB, studentC)
.map(Student::getId)
.collect(Collectors.joining("^_^"));
//使⽤^_^ 作为分隔符
//[]作为前后缀
//[20190001^_^20190002^_^20190003]
Stream.of(studentA, studentB, studentC)
.map(Student::getId)
.
collect(Collectors.joining("^_^", "[", "]"));
⽰例:
// Long 8
Stream.of(1,0,-10,9,8,100,200,-80)
.unting());
//如果仅仅只是为了统计,那就没必要使⽤Collectors了,那样更消耗资源
// long 8
Stream.of(1,0,-10,9,8,100,200,-80)
.count();
Collectors.minBy()、Collectors.maxBy() 和Stream.min()、Stream.max() 作⽤也是⼀样的,只不过Collectors.minBy()、Collectors.maxBy()适⽤于⾼级场景。
⽰例:
// maxBy 200
Stream.of(1, 0, -10, 9, 8, 100, 200, -80)
.collect(Collectors.maxBy(Integer::compareTo)).ifPresent(System.out::println);
// max 200
Stream.of(1, 0, -10, 9, 8, 100, 200, -80)
.max(Integer::compareTo).ifPresent(System.out::println);
// minBy -80
Stream.of(1, 0, -10, 9, 8, 100, 200, -80)
.collect(Collectors.minBy(Integer::compareTo)).ifPresent(System.out::println);
// min -80
Stream.of(1, 0, -10, 9, 8, 100, 200, -80)
.min(Integer::compareTo).ifPresent(System.out::println);
Collectors.summingInt()、Collectors.summarizingLong()、Collectors.summarizingDouble() 这三个分别⽤于int、long、double 类型数据⼀个求总操作,返回的是⼀个SummaryStatistics(求总),包含了数量统计count、求和sum、最⼩值min、平均值average、最⼤值max。
虽然IntStream、DoubleStream、LongStream 都可以是求和sum 但是也仅仅只是求和,没有summing结果丰富。如果要⼀次性统计、求平均值什么的,summing还是⾮常⽅便的。
⽰例:
//IntSummaryStatistics{count=10, sum=55, min=1, average=5.500000, max=10}
Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
.collect(Collectors.summarizingint(Integer::valueOf));
/
/DoubleSummaryStatistics{count=10, sum=55.000000, min=1.000000, average=5.500000, max=10.000000}
Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
.collect(Collectors.summarizingdouble(double::valueOf));
//LongSummaryStatistics{count=10, sum=55, min=1, average=5.500000, max=10}
Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
.collect(Collectors.summarizinglong(long::valueOf));
// 55
Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).mapToint(Integer::valueOf)
.sum();
// 55.0
Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).mapTodouble(double::valueOf)
.sum();
// 55
Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).mapTolong(long::valueOf)
.sum();
Collectors.averagingInt()、Collectors.averagingDouble()、Collectors.averagingLong() 求平均值,适⽤于⾼级场景,这个后⾯再提。
⽰例:
Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
.collect(Collectors.averagingint(Integer::valueOf));
Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
.collect(Collectors.averagingdouble(double::valueOf));
Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
.collect(Collectors.averaginglong(long::valueOf));
ducing() 好像也和duce()差不多,也都是规约操作。其实unting() 就是⽤reducing()实现的,如代码所⽰:
public static <T> Collector<T, ?, Long> counting() {
return reducing(0L, e -> 1L, Long::sum);
}
那既然这样的话,我们就实现⼀个对所有学⽣名字长度求和规约操作。
⽰例:
//Optional[6]
Stream.of(studentA, studentB, studentC)
.map(student -> student.name.length())
.ducing(Integer::sum));
//6
//或者这样,指定初始值,这样可以防⽌没有元素的情况下正常执⾏
Stream.of(studentA, studentB, studentC)
.map(student -> student.name.length())
.ducing(0, (i1, i2) -> i1 + i2));
//6
//更或者先不转换,规约的时候再转换
Stream.of(studentA, studentB, studentC)
.ducing(0, s -> ((Student) s).getName().length(), Integer::sum));
前后处理
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论