首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

Java 8 的 Lambda 表达式和流处理(3)流水线

Java 8 的 Lambda 表达式和流处理(3)流水线

流水线在对流进行处理时,不同的流操作以级联的方式形成处理流水线。一个流水线由一个源(source),0 到多个中间操作(intermediate        operation)和一个终结操作(terminal operation)完成。
  • 源:源是流中元素的来源。Java 提供了很多内置的源,包括数组、集合、生成函数和 I/O 通道等。
  • 中间操作:中间操作在一个流上进行操作,返回结果是一个新的流。这些操作是延迟执行的。
  • 终结操作:终结操作遍历流来产生一个结果或是副作用。在一个流上执行终结操作之后,该流被消费,无法再次被消费。
流的处理流水线在其终结操作运行时才开始执行。
源Java 8 支持从不同的源中创建流。Stream.of 方法可以使用给定的元素创建一个顺序流。使用 java.util.Arrays 的静态方法可以从数组中创建流,如清单5 所示。
清单 5.        从数组中创建流
1
2
3
4
5
6
7
8
Arrays.stream(new String[] {"Hello", "World"})
.forEach(System.out::println);
// 输出"Hello\nWorld"到控制台

int sum = Arrays.stream(new int[] {1, 2, 3})
.reduce((a, b) -> a + b)
.getAsInt();
// "sum"的值是"6"




接口 Collection 的默认方法 stream() 和 parallelStream() 可以分别从集合中创建顺序流和并行流,如清单          6 所示。
清单 6.        从集合中创建流
1
2
3
4
5
6
List<String> list = new ArrayList<>();
list.add("Hello");
list.add("World");
list.stream()
.forEach(System.out::println);
// 输出 Hello 和 World




中间操作流中间操作在应用到流上,返回一个新的流。下面列出了常用的流中间操作:
  • map:通过一个 Function 把一个元素类型为 T 的流转换成元素类型为 R 的流。
  • flatMap:通过一个 Function 把一个元素类型为 T 的流中的每个元素转换成一个元素类型为 R 的流,再把这些转换之后的流合并。
  • filter:过滤流中的元素,只保留满足由 Predicate 所指定的条件的元素。
  • distinct:使用 equals 方法来删除流中的重复元素。
  • limit:截断流使其最多只包含指定数量的元素。
  • skip:返回一个新的流,并跳过原始流中的前 N 个元素。
  • sorted:对流进行排序。
  • peek:返回的流与原始流相同。当原始流中的元素被消费时,会首先调用 peek 方法中指定的 Consumer 实现对元素进行处理。
  • dropWhile:从原始流起始位置开始删除满足指定 Predicate 的元素,直到遇到第一个不满足 Predicate 的元素。
  • takeWhile:从原始流起始位置开始保留满足指定 Predicate 的元素,直到遇到第一个不满足 Predicate 的元素。
清单 7 中,第一段代码展示了 flatMap 的用法,第二段代码展示了 takeWhile 和        dropWhile 的用法。
清单 7.        中间操作示例
1
2
3
4
5
6
7
8
9
10
11
Stream.of(1, 2, 3)
    .map(v -> v + 1)
    .flatMap(v -> Stream.of(v * 5, v * 10))
    .forEach(System.out::println);
//输出 10,20,15,30,20,40

Stream.of(1, 2, 3)
    .takeWhile(v -> v <  3)
    .dropWhile(v -> v <  2)
    .forEach(System.out::println);
//输出 2




终结操作终结操作产生最终的结果或副作用。下面是一些常见的终结操作。
forEach 和 forEachOrdered 对流中的每个元素执行由 Consumer 给定的实现。在使用 forEach        时,并没有确定的处理元素的顺序;forEachOrdered 则按照流的相遇顺序来处理元素,如果流有确定的相遇顺序的话。
reduce 操作把一个流约简成单个结果。约简操作可以有 3 个部分组成:
  • 初始值:在对元素为空的流进行约简操作时,返回值为初始值。
  • 叠加器:接受 2 个参数的 BiFunction。第一个参数是当前的约简值,第二个参数是当前元素,返回结果是新的约简值。
  • 合并器:对于并行流来说,约简操作可能在流的不同部分上并行执行。合并器用来把部分约简结果合并为最终的结果。
在清单 8 中,第一个 reduce 操作是最简单的形式,只需要声明叠加器即可。初始值是流的第一个元素;第二个        reduce 操作提供了初始值和叠加器;第三个 reduce 操作声明了初始值、叠加器和合并器。
清单 8. reduce 操        作示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Stream.of(1, 2, 3).reduce((v1, v2) -> v1 + v2)
    .ifPresent(System.out::println);
// 输出 6

int result1 = Stream.of(1, 2, 3, 4, 5)
    .reduce(1, (v1, v2) -> v1 * v2);
System.out.println(result1);
// 输出 120

int result2 = Stream.of(1, 2, 3, 4, 5)
    .parallel()
    .reduce(0, (v1, v2) -> v1 + v2, (v1, v2) -> v1 + v2);
System.out.println(result2);  
// 输出 15




Max 和 min 是两种特殊的约简操作,分别求得流中元素的最大值和最小值。
对于一个流,操作 allMatch、anyMatch 和 nonMatch 分别用来检查是否流中的全部元素、任意元素或没有元素满足给定的条件。判断的条件由 Predicate        指定。
操作 findFirst 和 findAny 分别查找流中的第一个或任意一个元素。两个方法的返回值都是 Optional 对象。当流为空时,返回的是空的 Optional        对象。如果一个流没有确定的相遇顺序,那么 findFirst 和 findAny 的行为在本质上是相同的。
操作 collect 表示的是另外一类的约简操作。与 reduce 不同在于,collect 会把结果收集到可变的容器中,如 List 或 Set。收集操作通过接口        java.util.stream.Collector 来实现。Java 已经在类 Collectors 中提供了很多常用的 Collector 实现。
第一类收集操作是收集到集合中,常见的方法有 toList()、toSet() 和 toMap() 等。第二类收集操作是分组收集,即使用 groupingBy        对流中元素进行分组。分组时对流中所有元素应用同一个 Function。具有相同结果的元素被分到同一组。分组之后的结果是一个 Map,Map 的键是应用 Function        之后的结果,而对应的值是属于该组的所有元素的 List。在清单 9        中,流中的元素按照字符串的第一个字母分组,所得到的 Map 中的键是 A、B 和 D,而 A 对应的 List 值中包含了 Alex 和 Amy 两个元素,B 和 D 所对应的        List 值则只包含一个元素。
清单 9. 收集器 groupingBy 示        例
1
2
3
final Map<Character, List<String>> names = Stream.of("Alex", "Bob", "David", "Amy")
    .collect(Collectors.groupingBy(v -> v.charAt(0)));
System.out.println(names);




第三类的 joining 操作只对元素类型为 CharSequence 的流使用,其作用是把流中的字符串连接起来。清单 10 中把字符串流用", "进行连接。
清单 10. 收集器 joining 示        例
1
2
3
String str = Stream.of("a", "b", "c")
   .collect(Collectors.joining(", "));
System.out.println(str);




第四类的 partitioningBy 操作的作用类似于 groupingBy,只不过分组时使用的是 Predicate,也就是说元素最多分成两组。所得到结果的 Map 的键的类型是        Boolean,而值的类型同样是 List。
还有一些收集器可以进行数学计算,不过只对元素类型为 int、long 或 double 的流可用。这些数学计算包括:
  • averagingDouble、averagingInt 和 averagingLong 计算流中元素的平均值。
  • summingDouble、summingInt 和 summingLong 计算流中元素的和。
  • summarizingDouble、summarizingInt 和 summarizingLong          对流中元素进行数学统计,可以得到平均值、数量、和、最大值和最小值。
清单 11 展示了这些数学计算相关的收集器的用法。
清单 11.        与数学计算相关的收集器
1
2
3
4
5
6
7
8
double avgLength = Stream.of("hello", "world", "a")
    .collect(Collectors.averagingInt(String::length));
System.out.println(avgLength);

final IntSummaryStatistics statistics = Stream.of("a", "b", "cd")
    .collect(Collectors.summarizingInt(String::length));
System.out.println(statistics.getAverage());
System.out.println(statistics.getCount());




Stream 中还有其他实用的操作,限于篇幅不能全部介绍。相关的用法可以查看 API 文档。
返回列表