Introduction to StreamEx – StreamEx简介

最后修改: 2017年 10月 14日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

One of the most exciting features of Java 8 is the Stream API – which, simply put, is a powerful tool for processing sequences of elements.

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

StreamEx is a library that provides additional functionality for the standard Stream API along with the performance improvements.

StreamEx是一个库,它为标准的Stream API提供了额外的功能以及性能的改进。

Here are a few core features:

以下是一些核心功能。

  • Shorter and convenient ways of doing the everyday tasks
  • 100% compatibility with original JDK Streams
  • Friendliness for parallel processing: any new feature takes the advantage on parallel streams as much as possible
  • Performance and minimal overhead. If StreamEx allows solving the task using less code compared to standard Stream, it should not be significantly slower than the usual way (and sometimes it’s even faster)

In this tutorial, we’ll present some of the features of StreamEx API.

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

2. Setting up the Example

2.设置实例

To use StreamEx, we need to add the following dependency to the pom.xml:

为了使用StreamEx,我们需要在pom.xml中添加以下依赖项。

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

The latest version of the library can be found on Maven Central.

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

Through this tutorial, we’re going to use a simple User class:

通过本教程,我们将使用一个简单的User类。

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

    // standard getters, setters, and constructors
}

And a simple Role class:

还有一个简单的Role类。

public class Role {
}

3. Collectors Shortcut Methods

3.收集器快捷方法

One of the most popular terminal operations of Streams is the collect operation; this allows for repackaging Stream elements to a collection of our choice.

Streams最受欢迎的终端操作之一是collect操作;这允许将Stream元素重新打包到我们选择的集合中。

The problem is that code can get unnecessarily verbose for simple scenarios:

问题是,对于简单的情况,代码可能会变得不必要的冗长。

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

3.1. Collecting to a Collection

3.1.收集到一个集合

Now, with StreamEx, we don’t need to provide a Collector to specify that we need a List, Set, Map, InmutableList, etc.:

现在,通过StreamEx,我们不需要提供一个Collector来指定我们需要一个ListSet、Map、InmutableList、等。

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

The collect operation is still available in the API if we want to perform something more complicated than taking elements from a Stream and putting them in a collection.

如果我们想执行比从Stream中获取元素并将其放入一个集合中更复杂的操作,API中仍然有collect操作。

3.2. Advanced Collectors

3.2.高级采集器

Another shorthand is groupingBy:

另一个速记方法是groupingBy

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

This will produce a Map with the key type specified in the method reference, producing something similar to the group by operation in SQL.

这将产生一个Map,其键类型在方法引用中指定,产生类似于SQL中的group by操作。

Using plain Stream API, we’d need to write:

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

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

A similar shorthand form can be found for the Collectors.joining():

对于Collectors.join(),可以找到类似的速记形式:

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

Which takes all the elements in the Stream a produces a String concatenating all of them.

它接收Stream中的所有元素,产生一个连接所有元素的String

4. Adding, Removing and Selecting Elements

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

In some scenarios, we’ve got a list of objects of different types, and we need to filter them by type:

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

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

We can add elements to the start or end of our Stream, with this handy operations:

我们可以在我们的Stream的开始或结束处添加元素,通过这个方便的操作。

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

We can remove unwanted null elements using nonNull() and use the Stream as an Iterable:

我们可以使用nonNull()删除不需要的空元素,并将Stream作为一个Iterable使用。

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

5. Math Operations and Primitive Types Support

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

StreamEx adds supports for primitive types, as we can see in this self-explaining example:

StreamEx增加了对原始类型的支持,正如我们在这个自我解释的例子中可以看到的那样。

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

Now let’s take an array of double elements in an unordered manner. We want to create an array consisting of the difference between each pair.

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

We can use the pairMap method to perform this operation:

我们可以使用pairMap方法来执行这一操作。

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

6. Map Operations

6.地图操作

6.1. Filtering by Keys

6.1.按键过滤

Another useful feature is an ability to create a Stream from a Map and filter the elements by using the values they point at.

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

In this case, we’re taking all non-null values:

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

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. Operating on Key-Value Pairs

6.2.对键值对的操作

We can also operate on key-value pairs by creating an EntryStream instance:

我们还可以通过创建一个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;
}

The special operation EntryStream.of takes a Map and transforms it into a Stream of key-value objects. Then we use flatMapValues operation to transform our list of roles to a Stream of single values.

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

Next, we can invert the key-value pair, making the User class the key and the Role class the value.

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

And finally, we can use the grouping operation to transform our map to the inversion of the one received, all with just four operations.

最后,我们可以使用分组操作,将我们的地图转化为所收到的地图的反转,所有这些都只需要四个操作。

6.3. Key-Value Mapping

6.3.键值映射

We can also map keys and values independently:

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

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

With this, we can quickly transform our keys or values to another required type.

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

7. File Operations

7.文件操作

Using StreamEx, we can read files efficiently, i.e., without loading full files at once. It’s handy while processing large files:

使用StreamEx,我们可以有效地读取文件,也就是说,不需要一次性加载全部文件。这在处理大文件时很方便。

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

Note that we’ve used remove() method to filter away empty lines.

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

Point to note here is that StreamEx won’t automatically close the file. Hence, we must remember to manually perform closing operation on both file reading and writing occasion to avoid unnecessary memory overhead.

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

8. Conclusion

8.结论

In this tutorial, we’ve learned about StreamEx, and it’s different utilities. There is a lot more to go through – and they have a handy cheat sheet here.

在本教程中,我们已经了解了StreamEx,以及它的不同工具。还有很多东西要去了解 – 他们有一个方便的小抄这里

As always, the full source code is available over on GitHub.

一如既往,完整的源代码可在GitHub上获得