Java 8中的Collectors API介绍

目录
  • Stream.Collect() 方法
  • Collectors
    • Collectors.ToList()
    • Collectors.ToUnmodifiableList()
    • Collectors.ToSet()
    • Collectors.ToUnmodifiableSet()
    • Collectors.ToCollection()
    • Collectors.ToMap()
    • Collectors.ToUnmodifiableMap()
    • Collectors.CollectingAndThen()
    • Collectors.Joining()
    • Collectors.Counting()
    • Collectors.SummarizingDouble/Long/Int()
    • Collectors.AveragingDouble/Long/Int()
    • Collectors.SummingDouble/Long/Int()
    • Collectors.MaxBy()/MinBy()
    • Collectors.GroupingBy()
    • Collectors.PartitioningBy()
    • Collectors.Teeing()
  • Custom Collectors

Stream.Collect() 方法

Stream.collect()是Java 8的流API的终端方法之一。它允许我们对流实例中保存的数据元素执行可变折叠操作(将元素重新打包到某些数据结构,并应用一些附加逻辑,将它们连接起来,等等)。

此操作的策略通过收集器接口实现提供。

Collectors

所有预定义的实现都可以在Collectors类中找到。通常使用以下静态导入来提高可读性:

import static java.util.stream.Collectors.*;

我们也可以使用我们选择的单一导入收集器collectors:

import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;
import static java.util.stream.Collectors.toSet;

在以下示例中,我们将重用以下list:

List<String> givenList = Arrays.asList("a", "bb", "ccc", "dd");

Collectors.ToList()

toList收集器可用于将所有流元素收集到列表实例中。需要记住的重要一点是,我们不能用这种方法假设任何特定的列表实现。如果我们想对此有更多的控制,我们可以使用toCollection

让我们创建一个表示元素序列的流实例,然后将它们收集到一个列表实例中:

List<String> result = givenList.stream()
  .collect(toList());

Collectors.ToUnmodifiableList()

Java 10引入了一种方便的方法,将流元素累积到一个不可修改的列表中:

List<String> result = givenList.stream()
  .collect(toUnmodifiableList());

现在,如果我们试图修改结果列表,我们将得到一个UnsupportedOperationException

assertThatThrownBy(() -> result.add("foo"))
  .isInstanceOf(UnsupportedOperationException.class);

Collectors.ToSet()

toSet收集器可用于将所有流元素收集到集合实例中。需要记住的重要一点是,我们不能用这种方法假设任何特定的集合实现。如果我们想对此有更多的控制,我们可以使用toCollection

让我们创建一个表示元素序列的流实例,然后将它们收集到一个集合实例中:

Set<String> result = givenList.stream()
  .collect(toSet());

集合不包含重复的元素。如果我们的集合包含彼此相等的元素,则它们只在结果集中出现一次:

List<String> listWithDuplicates = Arrays.asList("a", "bb", "c", "d", "bb");
Set<String> result = listWithDuplicates.stream().collect(toSet());
assertThat(result).hasSize(4);

Collectors.ToUnmodifiableSet()

自Java 10以来,我们可以使用toUnmodifiableSet()收集器轻松创建一个不可修改的集:

Set<String> result = givenList.stream()
  .collect(toUnmodifiableSet());

任何修改结果集的尝试都将以不支持操作异常告终:

assertThatThrownBy(() -> result.add("foo"))
  .isInstanceOf(UnsupportedOperationException.class);

Collectors.ToCollection()

正如我们已经指出的,在使用toSettoList收集器时,我们不能对它们的实现进行任何假设。如果我们想使用自定义实现,我们需要将toCollection收集器与我们选择的提供的集合一起使用。

让我们创建一个表示元素序列的流实例,然后将它们收集到LinkedList实例中:

List<String> result = givenList.stream()
  .collect(toCollection(LinkedList::new))

请注意,这不适用于任何不可变的集合。在这种情况下,我们需要编写自定义收集器实现或使用CollectionAndThen

Collectors.ToMap()

toMap收集器可用于将流元素收集到映射实例中。为此,我们需要提供两个功能:

  • keyMapper
  • valueMapper

我们将使用keyMapper从流元素中提取映射键,使用valueMapper提取与给定键关联的值。

让我们将这些元素收集到一个映射中,该映射将字符串存储为键,长度存储为值:

Map<String, Integer> result = givenList.stream()
  .collect(toMap(Function.identity(), String::length))

Function.identity()只是定义接受并返回相同值的函数的快捷方式。

那么,如果我们的集合包含重复的元素,会发生什么呢?与toSet相反,toMap不会默默地过滤重复项,这是可以理解的,因为它如何确定为该键选择哪个值?

List<String> listWithDuplicates = Arrays.asList("a", "bb", "c", "d", "bb");
assertThatThrownBy(() -> {
    listWithDuplicates.stream().collect(toMap(Function.identity(), String::length));
}).isInstanceOf(IllegalStateException.class);

请注意,toMap甚至不计算这些值是否相等。如果它看到重复的key,它会立即抛出一个非法状态异常。

在key冲突的情况下,我们应该使用另一个签名的toMap

Map<String, Integer> result = givenList.stream()
  .collect(toMap(Function.identity(), String::length, (item, identicalItem) -> item));

这里的第三个参数是BinaryOperator,我们可以在其中指定希望如何处理碰撞。在本例中,我们只选择这两个冲突的值中的任何一个,因为我们知道相同的字符串也总是具有相同的长度。

Collectors.ToUnmodifiableMap()

与列表和集合类似,Java 10引入了一种将流元素收集到不可修改映射中的简单方法:

Map<String, Integer> result = givenList.stream()
  .collect(toMap(Function.identity(), String::length))

正如我们所见,如果我们试图在结果映射中添加一个新条目,我们将得到一个不支持的操作异常:

assertThatThrownBy(() -> result.put("foo", 3))
  .isInstanceOf(UnsupportedOperationException.class);

Collectors.CollectingAndThen()

CollectionAndThen是一个特殊的收集器,允许我们在收集结束后立即对结果执行另一个操作。

让我们将流元素收集到列表实例,然后将结果转换为ImmutableList实例:

List<String> result = givenList.stream()
  .collect(collectingAndThen(toList(), ImmutableList::copyOf))

Collectors.Joining()

Joining收集器可用于joining Stream<String> 元素。

我们可以通过以下方式将它们结合在一起:

String result = givenList.stream()
  .collect(joining());

结果:

"abbcccdd"

我们还可以指定自定义分隔符、前缀和后缀:

String result = givenList.stream()
  .collect(joining(" "));

结果:

"a bb ccc dd"

也可这样写:

String result = givenList.stream()
  .collect(joining(" ", "PRE-", "-POST"));

结果:

"PRE-a bb ccc dd-POST"

Collectors.Counting()

Counting是一个简单的收集器,允许对所有流元素进行计数。

现在我们可以写:

Long result = givenList.stream()
  .collect(counting());

Collectors.SummarizingDouble/Long/Int()

SummaringDouble/Long/Int是一个收集器,它返回一个特殊的类,该类包含有关提取元素流中数字数据的统计信息。

我们可以通过以下操作获得有关字符串长度的信息:

DoubleSummaryStatistics result = givenList.stream()
  .collect(summarizingDouble(String::length));

在这种情况下,以下是正确的:

assertThat(result.getAverage()).isEqualTo(2);
assertThat(result.getCount()).isEqualTo(4);
assertThat(result.getMax()).isEqualTo(3);
assertThat(result.getMin()).isEqualTo(1);
assertThat(result.getSum()).isEqualTo(8);

Collectors.AveragingDouble/Long/Int()

AveragingDouble/Long/Int是一个收集器,它只返回提取元素的平均值。

我们可以通过以下操作获得平均字符串长度:

Double result = givenList.stream()
  .collect(averagingDouble(String::length));

Collectors.SummingDouble/Long/Int()

SummingDouble/Long/Int是一个收集器,它只返回提取元素的总和。

我们可以通过以下操作得到所有字符串长度的总和:

Double result = givenList.stream()
  .collect(summingDouble(String::length));

Collectors.MaxBy()/MinBy()

MaxBy/MinBy收集器根据提供的比较器实例返回流的最大/最小元素。

我们可以通过以下方式选择最大的元素:

Optional<String> result = givenList.stream()
  .collect(maxBy(Comparator.naturalOrder()));

我们可以看到返回的值被包装在一个可选的实例中。这迫使用户重新考虑空的收集角落案例。

Collectors.GroupingBy()

GroupingBy collector用于按某些属性对对象进行分组,然后将结果存储在Map实例中。

我们可以按字符串长度对它们进行分组,并将分组结果存储在集合实例中:

Map<Integer, Set<String>> result = givenList.stream()
  .collect(groupingBy(String::length, toSet()));

结果是true:

assertThat(result)
  .containsEntry(1, newHashSet("a"))
  .containsEntry(2, newHashSet("bb", "dd"))
  .containsEntry(3, newHashSet("ccc"));

我们可以看到groupingBy方法的第二个参数是收集器。此外,我们可以自由使用我们选择的任何收集器。

Collectors.PartitioningBy()

PartitioningBy是groupingBy的一种特殊情况,它接受谓词实例,然后将流元素收集到Map实例中,Map实例将布尔值存储为键,将集合存储为值。在“true”键下,我们可以找到与给定谓词匹配的元素集合,在“false”键下,我们可以找到与给定谓词不匹配的元素集合。

我们可以写:

Map<Boolean, List<String>> result = givenList.stream()
  .collect(partitioningBy(s -> s.length() > 2))

在Map中的结果:

{false=["a", "bb", "dd"], true=["ccc"]}

Collectors.Teeing()

让我们使用到目前为止所学的收集器,从给定流中找出最大和最小数:

List<Integer> numbers = Arrays.asList(42, 4, 2, 24);
Optional<Integer> min = numbers.stream().collect(minBy(Integer::compareTo));
Optional<Integer> max = numbers.stream().collect(maxBy(Integer::compareTo));
// do something useful with min and max

在这里,我们使用两个不同的收集器,然后将这两个收集器的结果结合起来,创造出一些有意义的东西。在Java12之前,为了涵盖此类用例,我们必须对给定流进行两次操作,将中间结果存储到临时变量中,然后将这些结果合并。

幸运的是,Java12提供了一个内置收集器,代表我们处理这些步骤;我们所要做的就是提供两个采集器和组合器功能。

由于这种新的收集器将给定的流转向两个不同的方向,因此称为T形:

numbers.stream().collect(teeing(
  minBy(Integer::compareTo), // The first collector
  maxBy(Integer::compareTo), // The second collector
  (min, max) -> // Receives the result from those collectors and combines them
));

Custom Collectors

如果我们想编写自己的收集器实现,我们需要实现收集器接口,并指定其三个通用参数:

public interface Collector<T, A, R> {...}
  • T–可供收集的对象类型
  • A–可变累加器对象的类型
  • R–最终结果的类型

让我们编写一个示例收集器,用于将元素收集到ImmutableSet实例中。我们首先指定正确的类型:

private class ImmutableSetCollector<T>
  implements Collector<T, ImmutableSet.Builder<T>, ImmutableSet<T>> {...}

因为我们需要一个可变集合来处理内部集合操作,所以不能使用ImmutableSet。相反,我们需要使用一些其他可变集合,或任何其他可以临时为我们积累对象的类。在这种情况下,我们将使用ImmutableSet。现在我们需要实现5种方法:

  • Supplier<ImmutableSet.Builder<T>> supplier()
  • BiConsumer<ImmutableSet.Builder<T>, T> accumulator()
  • BinaryOperator<ImmutableSet.Builder<T>> combiner()
  • Function<ImmutableSet.Builder<T>, ImmutableSet<T>> finisher()
  • Set<Characteristics> characteristics()

supplier()方法返回一个生成空累加器实例的Supplier实例。所以在这种情况下,我们可以简单地写:

@Override
public Supplier<ImmutableSet.Builder<T>> supplier() {
    return ImmutableSet::builder;
}

acculator()方法返回一个函数,该函数用于向现有acculator对象添加新元素。让我们使用生成器的add方法:

@Override
public BiConsumer<ImmutableSet.Builder<T>, T> accumulator() {
    return ImmutableSet.Builder::add;
}

combiner()方法返回一个用于将两个累加器合并在一起的函数:

@Override
public BinaryOperator<ImmutableSet.Builder<T>> combiner() {
    return (left, right) -> left.addAll(right.build());
}

finisher()方法返回一个函数,用于将累加器转换为最终结果类型。所以在这种情况下,我们只使用Builder的构建方法:

@Override
public Function<ImmutableSet.Builder<T>, ImmutableSet<T>> finisher() {
    return ImmutableSet.Builder::build;
}

characteristics()方法用于为Stream提供一些用于内部优化的附加信息。在这种情况下,我们不会注意元素在集合中的顺序。

@Override public Set<Characteristics> characteristics() {
    return Sets.immutableEnumSet(Characteristics.UNORDERED);
}

以下是完整的实现和用法:

public class ImmutableSetCollector<T>
  implements Collector<T, ImmutableSet.Builder<T>, ImmutableSet<T>> {

@Override
public Supplier<ImmutableSet.Builder<T>> supplier() {
    return ImmutableSet::builder;
}

@Override
public BiConsumer<ImmutableSet.Builder<T>, T> accumulator() {
    return ImmutableSet.Builder::add;
}

@Override
public BinaryOperator<ImmutableSet.Builder<T>> combiner() {
    return (left, right) -> left.addAll(right.build());
}

@Override
public Function<ImmutableSet.Builder<T>, ImmutableSet<T>> finisher() {
    return ImmutableSet.Builder::build;
}

@Override
public Set<Characteristics> characteristics() {
    return Sets.immutableEnumSet(Characteristics.UNORDERED);
}

public static <T> ImmutableSetCollector<T> toImmutableSet() {
    return new ImmutableSetCollector<>();
}

最后在action中:

List<String> givenList = Arrays.asList("a", "bb", "ccc", "dddd");

ImmutableSet<String> result = givenList.stream()
  .collect(toImmutableSet());

到此这篇关于Java 8中的Collectors API介绍的文章就介绍到这了,更多相关 Collectors API 内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • java8中的Collectors.groupingBy用法详解

    Collectors.groupingBy根据一个或多个属性对集合中的项目进行分组 数据准备: public Product(Long id, Integer num, BigDecimal price, String name, String category) { this.id = id; this.num = num; this.price = price; this.name = name; this.category = category; } Product prod1 = new

  • Java8中List转Map(Collectors.toMap) 的技巧分享

    前言 在实际项目中我们经常会用到 List 转 Map 操作,在过去我们可能使用的是 for 循环遍历的方式.举个例子: 先定义类: // 简单对象 @Accessors(chain = true) // 链式方法 @lombok.Data class User { private String id; private String name; } 然后有这样一个 List: List<User> userList = Lists.newArrayList( new User().setId(

  • Java 8 Stream 的终极技巧——Collectors 功能与操作方法详解

    本文实例讲述了Java 8 Stream 的终极技巧--Collectors 功能与操作方法.分享给大家供大家参考,具体如下: 1. 前言 昨天在 Collection移除元素操作 相关的文章中提到了 Collectors .相信很多同学对这个比较感兴趣,那我们今天就来研究一下 Collectors . 2. Collectors 的作用 Collectors 是 Java 8 加入的操作类,位于 java.util.stream 包下.它会根据不同的策略将元素收集归纳起来,比如最简单常用的是将

  • Java8 Collectors求和功能的自定义扩展操作

    业务中需要将一组数据分类后收集总和,原本可以使用Collectors.summingInt(),但是我们的数据源是BigDecimal类型的,而Java8原生只提供了summingInt.summingLong.summingDouble三种基础类型的方法. 于是就自己动手丰衣足食吧.. 自定义工具类 public class MyCollectors { private MyCollectors() { } // public static <T> Collector<T, ?, Bi

  • Java9 Stream Collectors新增功能(小结)

    Java 9 Stream Collectors新增功能 Java 8 引入Collectors,用于累加输入元素至可变的容器如,Map.List以及Set.本文看看Java 9 新增的两个Collectors:Collectors.filtering 和 Collectors.flatMapping,主要用于和 Collectors.groupingBy 一起提供智能的元素集合. Collectors.filtering方法 Collectors.filtering方法类似于Stream fi

  • 解决JAVA8 Collectors.toMap value为null报错的问题

    2018年11月7日 17:59:27 该bug貌似在java9中修复,欢迎补充 2019年3月19日 17:59:11 查看java11的toMap方法后,发现并没有修改任何实现 Caused by: java.lang.NullPointerException java.util.HashMap.merge(HashMap.java:1224) java.util.stream.Collectors.lambda$toMap$58(Collectors.java:1320) java.uti

  • Java8 Collectors.toMap的坑

    按照常规思维,往一个map里put一个已经存在的key,会把原有的key对应的value值覆盖,然而通过一次线上问题,发现Java8中的Collectors.toMap反其道而行之,它默认给抛异常,抛异常... 线上业务代码出现Duplicate Key的异常,影响了业务逻辑,查看抛出异常部分的代码,类似以下写法: Map<Integer, String> map = list.stream().collect(Collectors.toMap(Person::getId, Person::g

  • Java8 Stream Collectors收集器使用方法解析

    Collectors.toMap: Student studentA = new Student("20190001","小明"); Student studentB = new Student("20190002","小红"); Student studentC = new Student("20190003","小丁"); //Function.identity() 获取这个对象本身

  • Java 8中的Collectors API介绍

    目录 Stream.Collect() 方法 Collectors Collectors.ToList() Collectors.ToUnmodifiableList() Collectors.ToSet() Collectors.ToUnmodifiableSet() Collectors.ToCollection() Collectors.ToMap() Collectors.ToUnmodifiableMap() Collectors.CollectingAndThen() Collect

  • Java阻塞队列四组API介绍(小结)

    通过前面几篇文章的学习,我们已经知道了Java中的队列分为阻塞队列和非阻塞队列以及常用的七个阻塞队列.如下图: 本文来源:凯哥Java(kaigejava)讲解Java并发系列之阻塞队列教程.系列文章,欢迎大家从第一篇文章开始看起. 在查看以上七个队列的API的时候,我们可以很明显的看到以下四组API: add()/remove()/remove offer()/poll()/peek() put/take() offer(e,time,unit)/poll(time,unit). 分别对应的是

  • Java编程中的构造函数详细介绍

    本文主要是为新手.对java语言感兴趣的人和那些没有系统学习过java基础知识的人进行一个总结,在文章中对构造函数进行了较为详细的说明和讨论,也包含了我个人对于java面向对象中构造函数的一些看法.希望走在java学习道路上的同行者可以有一个较为清晰的认知和理解.当然仅为个人观点,水平有限,不足之处,还请大家多多指出,互相交流学习. 1.构造函数的概念 很多java新手谈到构造函数就会犯晕,我们先来看看什么是构造函数. 首先,构造函数是函数的一种特殊形式,特殊在哪里?构造函数中不需要定义返回类型

  • java多态中的就近原则介绍

    直接上题: 题目补充: class A { int m; //-10 int getM() { return m; } int seeM() { return m; } } class B extends A { int m ; //10 int getM() { // System.out.println(this.m+"............"+super.m); return m+10; } } public class Test { public static void ma

  • java ArrayList中的remove方法介绍

    先看一段代码,看看自定义的ArrayList中的remove设计是否有问题. public class MyArrayList { private Object[] mData = new Object[0]; private int mSize = 0; // 删除第i个元素 public void remove(int i) { if (i < 0 || i >= mSize) return; for (int index = i; index < mSize - 1; index+

  • Java编程中使用JDBC API连接数据库和创建程序的方法

    JDBC连接数据库 涉及到建立一个JDBC连接的编程是相当简单的.下面是这些简单的四个步骤: 导入JDBC包: 添加import语句到Java程序导入所需的类在Java代码中. 注册JDBC驱动程序:这一步会导致JVM加载所需的驱动程序实现到内存中,因此它可以实现JDBC请求. 数据库URL制定:这是创建格式正确的地址指向到要连接的数据库. 创建连接对象:最后,代码调用DriverManager对象的getConnection()方法来建立实际的数据库连接. 导入JDBC包: import 语句

  • Java 8中Stream API的这些奇技淫巧!你Get了吗?

    上次老师跟大家分享了 cookie.session和token,今天给大家分享一下Java 8中的Stream API. Stream简介 1.Java 8引入了全新的Stream API.这里的Stream和I/O流不同,它更像具有Iterable的集合类,但行为和集合类又有所不同. 2.stream是对集合对象功能的增强,它专注于对集合对象进行各种非常便利.高效的聚合操作,或者大批量数据操作. 3.只要给出需要对其包含的元素执行什么操作,比如 "过滤掉长度大于 10 的字符串".&

  • Java 8 中 Function 接口使用方法介绍

    目录 Java 8 中 Function 接口的介绍 Function 接口的用法 Function 接口的实例 Java 8 中 Function 接口的介绍 Java 8 中提供了一个函数式接口 Function,这个接口表示对一个参数做一些操作然后返回操作之后的值.这个接口的有一个抽象方法 apply,这个方法就是表明对参数做的操作. // Java Function 接口的定义 @FunctionalInterface public interface Function<T, R> {

  • 教你在JNA中将本地方法映射到JAVA代码中的示例

    目录 简介 Library Mapping Function Mapping Invocation Mapping 防止VM崩溃 性能考虑 总结 简介 不管是JNI还是JNA,最终调用的都是native的方法,但是对于JAVA程序来说,一定需要一个调用native方法的入口,也就是说我们需要在JAVA方法中定义需要调用的native方法. 对于JNI来说,我们可以使用native关键字来定义本地方法.那么在JNA中有那些在JAVA代码中定义本地方法的方式呢? Library Mapping 要想

  • Java中Lambda表达式用法介绍

    Lambda lambda是一个匿名函数,我们可以把lambda表达式理解为是一段可以传递的代码. lambda简明的地将代码或方法作为参数传递进去执行. "函数式编程"其核心是把函数作为值. 函数式接口 :只有一个 抽象方法的接口 称之为 函数式接口.函数式接口可以使用@FunctionalInterface进行注解. lambda表达式拆分为两部分 左侧:lambda 表达式的参数列表 右侧:lambda 表达式中所需要执行的功能,即lambda体 语法格式一:无参数,无返回值 @

随机推荐