StreamEx简介

2025/03/30

1. 概述

Stream API是Java 8最令人兴奋的特性之一-简单地说,它是处理元素序列的强大工具。

StreamEx是一个为标准Stream API提供附加功能以及性能改进的库。

以下是一些核心功能:

  • 更短更方便的日常任务处理方式
  • 与原始JDK Stream 100%兼容
  • 并行处理的友好性:任何新功能都尽可能地利用并行流
  • 性能和最小的开销,如果StreamEx允许使用与标准Stream相比更少的代码来解决任务,那么它应该不会比通常的方式慢很多(有时甚至更快)

在本教程中,我们将介绍StreamEx API的一些功能。

2. 设置示例

要使用StreamEx,我们需要将以下依赖添加到pom.xml:

<dependency>
    <groupId>one.util</groupId>
    <artifactId>streamex</artifactId>
    <version>0.6.5</version>
</dependency>

可以在Maven Central上找到该库的最新版本。

在本教程中,我们将使用一个简单的User类:

public class User {
    int id;
    String name;
    Role role = new Role();

    // standard getters, setters, and constructors
}

还有一个简单的Role类:

public class Role {
}

3. 收集器快捷方法

Stream最流行的终端操作之一是收集操作;这允许将Stream元素重新打包到我们选择的集合中。

问题是对于简单的场景,代码可能会变得不必要的冗长:

users.stream()
    .map(User::getName)
    .collect(Collectors.toList());

3.1 收集到集合

现在,有了StreamEx,我们不需要提供Collector来指定我们需要List、Set、Map、InmutableList等:

List<String> userNames = StreamEx.of(users)
    .map(User::getName)
    .toList();

如果我们想要执行比从Stream中获取元素并将它们放入集合中更复杂的操作,那么collect操作在API中仍然可用

3.2 高级收集器

另一个简写是groupingBy:

Map<Role, List<User>> role2users = StreamEx.of(users)
    .groupingBy(User::getRole);

这将生成一个具有方法引用中指定的键类型的Map,生成类似于SQL中的group by操作的内容。

使用普通的Stream API,我们需要编写:

Map<Role, List<User>> role2users = users.stream()
    .collect(Collectors.groupingBy(User::getRole));

对于Collectors.joining()也可以找到类似的简写形式:

StreamEx.of(1, 2, 3)
    .joining("; "); // "1; 2; 3"

它获取Stream中的所有元素并生成一个将它们全部拼接起来的字符串。

4. 添加、删除和选择元素

在某些场景下,我们有一个不同类型的对象列表,我们需要按类型过滤它们

List usersAndRoles = Arrays.asList(new User(), new Role());
List<Role> roles = StreamEx.of(usersAndRoles)
    .select(Role.class)
    .toList();

我们可以通过以下方便的操作将元素添加到Stream的开头或结尾

List<String> appendedUsers = StreamEx.of(users)
    .map(User::getName)
    .prepend("(none)")
    .append("LAST")
    .toList();

我们可以使用nonNull()删除不需要的null元素,并将Stream用作Iterable

for (String line : StreamEx.of(users).map(User::getName).nonNull()) {
    System.out.println(line);
}

5. 数学运算和原始类型支持

StreamEx添加了对原始类型的支持,正如我们在这个不言自明的例子中看到的那样:

short[] src = {1,2,3};
char[] output = IntStreamEx.of(src)
    .map(x -> x * 5)
    .toCharArray();

现在让我们以无序的方式获取一个double元素数组,我们想要创建一个由每对之间的差值组成的数组。

我们可以使用pairMap方法来执行这个操作:

public double[] getDiffBetweenPairs(double... numbers) {
    return DoubleStreamEx.of(numbers)
        .pairMap((a, b) -> b - a)
        .toArray();
}

6. Map操作

6.1 按键过滤

另一个有用的功能是能够从Map创建Stream并使用它们指向的值过滤元素。

在这种情况下,我们取所有非空值:

Map<String, Role> nameToRole = new HashMap<>();
nameToRole.put("first", new Role());
nameToRole.put("second", null);
Set<String> nonNullRoles = StreamEx.ofKeys(nameToRole, Objects::nonNull)
    .toSet();

6.2 对键值对进行操作

我们还可以通过创建一个EntryStream实例来操作键值对:

public Map<User, List<Role>> transformMap( 
    Map<Role, List<User>> role2users) {
    Map<User, List<Role>> users2roles = EntryStream.of(role2users)
        .flatMapValues(List::stream)
        .invert()
        .grouping();
    return users2roles;
}

特殊操作EntryStream.of获取一个Map并将其转换为键值对象的Stream,然后我们使用flatMapValues操作将我们的角色列表转换为单值流。

接下来,我们可以反转键值对,使User类成为键,Role类成为值。

最后,我们可以使用grouping操作将我们的Map转换为接收到的Map的反转,所有这些只需4个操作。

6.3 键值映射

我们还可以独立映射键和值:

Map<String, String> mapToString = EntryStream.of(users2roles)
    .mapKeys(String::valueOf)
    .mapValues(String::valueOf)
    .toMap();

有了它,我们可以快速将我们的键或值转换为另一种所需的类型。

7. 文件操作

使用StreamEx,我们可以高效地读取文件,即无需一次加载完整文件。它在处理大文件时非常方便:

StreamEx.ofLines(reader)
    .remove(String::isEmpty)
    .forEach(System.out::println);

请注意,我们使用了remove()方法来过滤掉空行。

这里需要注意的是StreamEx不会自动关闭文件,因此,我们一定要记得在读写文件的时候手动执行关闭操作,以避免不必要的内存开销。

8. 总结

在本教程中,我们了解了StreamEx及其不同的实用程序。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章