Java8Stream中Collectors的操作
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
Java8Stream中Collectors的操作
Collectors是java.util.stream包下的⼀个⼯具类,其中各个⽅法的返回值可以作为java.util.stream.Stream#collect的⼊参,实现对队列的各种操作,包括:分组、聚合等。
准备
定义Student类(⽤到了 lombok):
@Data
@AllArgsConstructor
public class Student {
private String id;
private String name;
private LocalDate birthday;
private int age;
private double score;
}
定义⼀组测试数据:
inal List<Student> students = Lists.newArrayList();
students.add(new Student("1", "张三", LocalDate.of(2009, Month.JANUARY, 1), 12, 12.123));
students.add(new Student("2", "李四", LocalDate.of(2010, Month.FEBRUARY, 2), 11, 22.123));
students.add(new Student("3", "王五", LocalDate.of(2011, Month.MARCH, 3), 10, 32.123));
数据统计
元素数量:counting
students.stream().collect(Collectors.counting())
平均值:averagingDouble、averagingInt、averagingLong
这⼏个⽅法是计算聚合元素的平均值,区别是输⼊参数需要是对应的类型。
⽐如,求学⽣的分数平均值,因为分数是double类型,所以在不转类型的情况下,需要使⽤averagingDouble:
students.stream().collect(Collectors.averagingDouble(Student::getScore))
如果考虑转换精度,也是可以实现:
// 精度转换:
students.stream().collect(Collectors.averagingInt(s -> (int)s.getScore()))
students.stream().collect(Collectors.averagingLong(s -> (long)s.getScore()))
// 如果是求学⽣的平均年龄,因为年龄是int类型,就可以随意使⽤任何⼀个函数了:
students.stream().collect(Collectors.averagingInt(Student::getAge))
students.stream().collect(Collectors.averagingDouble(Student::getAge))
students.stream().collect(Collectors.averagingLong(Student::getAge))
// 注意:这三个⽅法的返回值都是Double类型。
和:summingDouble、summingInt、summingLong
这三个⽅法和上⾯的平均值⽅法类似,也是需要注意元素的类型,在需要类型转换时,需要强制转换:
students.stream().collect(Collectors.summingInt(s -> (int)s.getScore()))
// 66.369
students.stream().collect(Collectors.summingDouble(Student::getScore))
// 66
students.stream().collect(Collectors.summingLong(s -> (long)s.getScore()))
summingDouble返回的是Double类型、summingInt返回的是Integer类型,summingLong返回的是Long类型。
最⼤值/最⼩值元素:maxBy、minBy
这两个函数就是求聚合元素中指定⽐较器中的最⼤/最⼩元素。
⽐如,求年龄最⼤/最⼩的Student对象:
students.stream().collect(Collectors.minBy(paring(Student::getAge)))
students.stream().collect(Collectors.maxBy(paring(Student::getAge)))
统计结果:summarizingDouble、summarizingInt、summarizingLong
students.stream().collect(Collectors.summarizingInt(s -> (int) s.getScore()))
students.stream().collect(Collectors.summarizingDouble(Student::getScore))
students.stream().collect(Collectors.summarizingLong(s -> (long) s.getScore()))
summarizingDouble返回DoubleSummaryStatistics类型,summarizingInt返回IntSummaryStatistics类型,summarizingLong返回
LongSummaryStatistics类型。
聚合、分组
聚合元素:toList、toSet、toCollection
这⼏个函数是将聚合之后的元素,重新封装到队列中,然后返回。
⽐如,得到所有Student的 ID 列表,只需要根据需要的结果类型使⽤不同的⽅法即可:
// List: [1, 2, 3]
final List<String> idList = students.stream().map(Student::getId).collect(Collectors.toList());
// Set: [1, 2, 3]
final Set<String> idSet = students.stream().map(Student::getId).collect(Collectors.toSet());
// TreeSet: [1, 2, 3]
final Collection<String> idTreeSet = students.stream().map(Student::getId).collect(Collectors.toCollection(TreeSet::new));
toList⽅法返回的是List⼦类,toSet返回的是Set⼦类,toCollection返回的是Collection⼦类。
Collection的⼦类包括List、Set等众多⼦类,所以toCollection更加灵活。
聚合元素:toMap、toConcurrentMap
这两个⽅法的作⽤是将聚合元素,重新组装为Map结构,也就是 k-v 结构。
两者⽤法⼀样,区别是toMap返回的是Map,toConcurrentMap返回ConcurrentMap,也就是说,toConcurrentMap返回的是线程安全的 Map 结构。
⽐如,我们需要聚合Student的 id:
final Map<String, Student> map11 = students.stream().collect(Collectors.toMap(Student::getId, Function.identity()));
但是,如果 id 有重复的,会抛出ng.IllegalStateException: Duplicate key异常,所以,为了保险起见,我们需要借助toMap另⼀个重载⽅法:
final Map<String, Student> map2 = students.stream().collect(Collectors.toMap(Student::getId, Function.identity(), (x, y) -> x));
可以看到,toMap有不同的重载⽅法,可以实现⽐较复杂的逻辑。
⽐如,我们需要得到根据 id 分组的Student的姓名:
final Map<String, String> map3 = students.stream()
.collect(Collectors.toMap(Student::getId, Student::getName, (x, y) -> x));
⽐如,我们需要得到相同年龄得分最⾼的Student对象集合:
final Map<Integer, Student> map5 = students.stream()
.collect(Collectors.toMap(Student::getAge, Function.identity(), BinaryOperator.maxBy(paring(Student::getScore))));
分组:groupingBy、groupingByConcurrent
groupingBy与toMap都是将聚合元素进⾏分组,区别是,toMap结果是 1:1 的 k-v 结构,groupingBy的结果是 1:n 的 k-v 结构。
⽐如,我们对Student的年龄分组:
final Map<Integer, List<Student>> map1 = students.stream().collect(Collectors.groupingBy(Student::getAge));
final Map<Integer, Set<Student>> map12 = students.stream().collect(Collectors.groupingBy(Student::getAge, Collectors.toSet()));
既然groupingBy也是分组,是不是也能够实现与toMap类似的功能,⽐如,根据 id 分组的Student:
final Map<String, Student> map3 = students.stream()
.collect(Collectors.groupingBy(Student::getId, Collectors.collectingAndThen(Collectors.toList(), list -> list.get(0))));
// 对⽐toMap
final Map<String, Student> map2 = students.stream()
.collect(Collectors.toMap(Student::getId, Function.identity(), (x, y) -> x));
// 如果想要线程安全的Map,可以使⽤groupingByConcurrent。
分组:partitioningBy
partitioningBy与groupingBy的区别在于,partitioningBy借助Predicate断⾔,可以将集合元素分为true和false两部分。
⽐如,按照年龄是否⼤于 11 分组:
final Map<Boolean, List<Student>> map6 = students.stream().collect(Collectors.partitioningBy(s -> s.getAge() > 11));
final Map<Boolean, Set<Student>> map7 = students.stream().collect(Collectors.partitioningBy(s -> s.getAge() > 11, Collectors.toSet()));链接数据:joining
这个⽅法对String类型的元素进⾏聚合,拼接成⼀个字符串返回,作⽤与ng.String#join类似,提供了 3 个不同重载⽅法,可以实现不同的需要。
⽐如:Stream.of("java", "go", "sql").collect(Collectors.joining());
Stream.of("java", "go", "sql").collect(Collectors.joining(", "));
Stream.of("java", "go", "sql").collect(Collectors.joining(", ", "【", "】"));
// list对象⾥⾯的name⽤逗号拼接起来
String name = students.stream().map(Student::getName).collect(Collectors.joining(","));
操作链:collectingAndThen
这个⽅法在groupingBy的例⼦中出现过,它是先对集合进⾏⼀次聚合操作,然后通过Function定义的函数,对聚合后的结果再次处理。
⽐如groupingBy中的例⼦:
final Map<String, Student> map3 = students.stream()
.collect(Collectors.groupingBy(Student::getId, Collectors.collectingAndThen(Collectors.toList(), list -> list.get(0))));
显⽰将结果聚合成List列表,然后取列表的第 0 个元素返回,通过这种⽅式,实现 1:1 的 map 结构。
再来⼀个复杂⼀些的,找到聚合元素中年龄数据正确的Student列表:
students.stream()
.filter(s -> (LocalDate.now().getYear() - s.getBirthday().getYear()) != s.getAge())
.collect(Collectors.toList());
操作后聚合:mapping
mapping先通过Function函数处理数据,然后通过Collector⽅法聚合元素。
⽐如,获取students的姓名列表:
students.stream()
.collect(Collectors.mapping(Student::getName, Collectors.toList()));
// 这种计算与java.util.stream.Stream#map⽅式类似:
students.stream().map(Student::getName).collect(Collectors.toList());
// 从这点上看,还是通过java.util.stream.Stream#map更清晰⼀些。