Java 8 Stream API简单指南

  首先不应将Java 8 Streams与Java I / O流混淆(例如:FileInputStream等); 这些彼此之间没什么关系。简而言之,流Stream是数据源周围的包装器,允许我们使用该数据源进行操作,并使批量处理方便快捷。

流不存储数据,从这个意义上讲,它不是数据结构。它也从不修改底层数据源。

新功能 - java.util.stream - 支持对元素流的函数样式操作,例如集合上的map-reduce转换。

现在让我们深入研究流创建和使用的几个简单示例 :

流创建

我们首先从现有数组中获取流:

    private static Employee[] arrayOfEmps = {
new Employee(1, "Jeff Bezos", 100000.0),
new Employee(2, "Bill Gates", 200000.0),
new Employee(3, "Mark Zuckerberg", 300000.0)
};

Stream.of(arrayOfEmps);

从一个List可获得流,注意List需要指定泛型类型:

private static List<Employee> empList = Arrays.asList(arrayOfEmps);  
empList.stream();

我们可以使用Stream.of()从各个对象创建一个流:

Stream.of(arrayOfEmps[0], arrayOfEmps[1], arrayOfEmps[2]);

或简单使用Stream.builder():

    Stream.Builder<Employee> empStreamBuilder = Stream.builder();

empStreamBuilder.accept(arrayOfEmps[0]);
empStreamBuilder.accept(arrayOfEmps[1]);
empStreamBuilder.accept(arrayOfEmps[2]);

Stream<Employee> empStream = empStreamBuilder.build();

流操作

现在让我们看看我们可以执行的一些常见用法和操作,并借助语言中的新流支持。

forEach

forEach()是最简单和最常见的操作; 它遍历流元素,在每个元素上调用提供的函数。

该方法非常常见,直接在Iterable,Map等中引入:
empList.stream().forEach(e -> e.salaryIncrement(10.0));

assertThat(empList, contains(
hasProperty("salary", equalTo(110000.0)),
hasProperty("salary", equalTo(220000.0)),
hasProperty("salary", equalTo(330000.0))
));

这将有效地调用salaryIncrement()中的每个元素empList

forEach()是一个终端操作,这意味着在执行操作之后,流管道被认为已被占用,并且无法再使用。我们将在下一节中详细讨论终端操作。

map

map()在将函数应用于原始流的每个元素后生成新流。新流可以是不同类型的。

以下示例将Integer的流转换为Employee的流:

Integer[] empIds = { 1, 2, 3 };

List<Employee> employees = Stream.of(empIds)
.map(employeeRepository::findById)
.collect(Collectors.toList());

assertEquals(employees.size(), empIds.length);

在这里,我们从数组中获取员工ID 的整数流。每个Integer都传递给函数employeeRepository :: findById() - 它返回相应的Employee对象; 这有效地形成了一个Employee流。

collect

我们在上一个例子中看到了collect()的工作原理; 这是我们完成所有处理后将一些东西从流中取出的常用方法之一:

List<Employee> employees = empList.stream().collect(Collectors.toList());

assertEquals(empList, employees);

collect()Stream实例中保存的数据元素执行可变折叠操作(将元素重新打包到某些数据结构并应用一些额外的逻辑,连接它们等)。

此操作的策略通过Collector接口实现提供。在上面的示例中,我们使用toList收集器将所有Stream元素收集到List实例中。

filter

接下来,我们来看看filter() ; 这会生成一个新流,其中包含通过指定测试(由Predicate指定返回True/false)的原始流的元素。

我们来看看它是如何工作的:

Integer[] empIds = { 1, 2, 3, 4 };

List<Employee> employees = Stream.of(empIds)
.map(employeeRepository::findById)
.filter(e -> e != null)
.filter(e -> e.getSalary() > 200000)
.collect(Collectors.toList());

assertEquals(Arrays.asList(arrayOfEmps[2]), employees);

在上面的示例中,我们首先过滤掉无效员工ID的引用,然后再次应用过滤器,以仅保留员工的工资超过特定阈值。

FindFirst

findFirst()为流中的第一个条目返回一个Optional ; Optional可以是空的:

Integer[] empIds = { 1, 2, 3, 4 };

Employee employee = Stream.of(empIds)
.map(employeeRepository::findById)
.filter(e -> e != null)
.filter(e -> e.getSalary() > 100000)
.findFirst()
.orElse(null);

assertEquals(employee.getSalary(), new Double(200000));

在这里,返回薪水大于100000的第一名员工。如果不存在此类员工,则返回null

toArray

我们看到了我们如何使用collect()从流中获取数据。如果我们需要从流中获取数组,我们可以简单地使用toArray()

 Employee[] employees = empList.stream().toArray(Employee[]::new);  

flatMap

流可以保存复杂的数据结构,如Stream <List <String >>。在这种情况下,flatMap()帮助我们扁平化数据结构以简化进一步的操作:

List<List<String>> namesNested = Arrays.asList(
Arrays.asList("Jeff", "Bezos"),
Arrays.asList("Bill", "Gates"),
Arrays.asList("Mark", "Zuckerberg"));

List<String> namesFlatStream = namesNested.stream()
.flatMap(Collection::stream)
.collect(Collectors.toList());

assertEquals(namesFlatStream.size(), namesNested.size() * 2);

 

请注意我们如何使用flatMap() API 将Stream <List <String >>转换为更简单的Stream <String>

peek

我们在本节前面看到了forEach(),这是一个终端操作。但是,有时我们需要在应用任何终端操作之前对流的每个元素执行多个操作。

peek()在这种情况下很有用。简单地说,它对流的每个元素执行指定的操作,并返回一个可以进一步使用的新流。peek()是一个中间操作

Employee[] arrayOfEmps = {
new Employee(1, "Jeff Bezos", 100000.0),
new Employee(2, "Bill Gates", 200000.0),
new Employee(3, "Mark Zuckerberg", 300000.0)
};

List<Employee> empList = Arrays.asList(arrayOfEmps);

empList.stream()
.peek(e -> e.salaryIncrement(10.0))
.peek(System.out::println)
.collect(Collectors.toList());

assertThat(empList, contains(
hasProperty("salary", equalTo(110000.0)),
hasProperty("salary", equalTo(220000.0)),
hasProperty("salary", equalTo(330000.0))
));

这里,第一个peek()用于增加每个员工的工资。第二个peek()用于打印员工。最后,collect()用作终端操作。

Java基础