首先不应将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() 是最简单和最常见的操作; 它遍历流元素,在每个元素上调用提供的函数。
该方法非常常见,直接在 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() 在将函数应用于原始流的每个元素后生成新流。新流可以是不同类型的。
以下示例将 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()的 工作原理; 这是我们完成所有处理后将一些东西从流中取出的常用方法之一:
List<Employee> employees = empList.stream().collect(Collectors.toList()); assertEquals(empList, employees);
collect() 对 Stream 实例中保存的数据元素执行可变折叠操作(将元素重新打包到某些数据结构并应用一些额外的逻辑,连接它们等)。
此操作的策略通过 Collector 接口实现提供。在上面的示例中,我们使用 toList 收集器将所有 Stream 元素收集到 List 实例中。
接下来,我们来看看 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() 为流中的第一个条目返回一个 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 。
我们看到了我们如何使用 collect() 从流中获取数据。如果我们需要从流中获取数组,我们可以简单地使用 toArray() :
Employee[] employees = empList.stream().toArray(Employee[]::new);
流可以保存复杂的数据结构,如 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> 。
我们在本节前面看到了 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() 用作终端操作。