Java 8 引入了一个新的抽象概念,叫做流(Stream)。流可以让你以一种声明式的方式处理数据,类似于 SQL 语句。流不仅可以操作集合,还可以操作数组、文件、生成器等数据源。流还支持并行处理,可以充分利用多核 CPU 的性能。
本文将介绍 Java 8 流式编程的基本概念和常用方法,帮助你掌握流式编程的精髓。
什么是流?
流(Stream)是一个来自数据源的元素序列,并支持聚合操作。流有以下几个特点:
- 流不存储元素,而是按需计算。
- 流的操作不会改变源数据,而是返回一个新的流。
- 流的操作是延迟执行的,只有当需要结果时才会执行。
- 流支持内部迭代,无需显示地遍历元素。
- 流支持串行和并行两种模式。
如何创建流?
创建流的方式有很多,常见的有以下几种:
- 从集合或数组创建流。例如,
List
list = Arrays.asList("a", "b", "c"); Stream stream = list.stream(); - 从 Stream 类的静态方法创建流。例如,
Stream
stream = Stream.of("a", "b", "c"); - 从文件或 I/O 通道创建流。例如,
Stream
stream = Files.lines(Paths.get("data.txt")); - 从 Random 或其他生成器创建流。例如,
Stream
stream = Stream.generate(() -> new Random().nextInt(100)); - 从其他类型的流创建流。例如,
IntStream intStream = stream.mapToInt(String::length);
如何操作流?
流提供了很多有用的方法来对元素进行操作,这些方法可以分为两类:中间操作(Intermediate Operation)和终端操作(Terminal Operation)。
中间操作会返回一个新的流,可以链式调用多个中间操作。中间操作是惰性的,只有当遇到终端操作时才会执行。
终端操作会产生一个结果或副作用,比如计算平均值、求和、打印输出等。终端操作会消耗掉流,执行完终端操作后,流就不能再使用了。
下面介绍一些常用的中间操作和终端操作。
中间操作
- filter:根据条件过滤元素。例如,
stream.filter(s -> s.length() > 3)
表示只保留长度大于3的字符串。 - map:对每个元素进行映射,生成一个新的元素。例如,
stream.map(String::toUpperCase)
表示把每个字符串转换成大写。 - flatMap:对每个元素进行映射,生成一个新的流,并把所有的流合并成一个流。例如,
stream.flatMap(s -> Arrays.stream(s.split("")))
表示把每个字符串拆分成字符,并合并成一个字符流。 - distinct:去除重复的元素。例如,
stream.distinct()
表示去除重复的字符串。 - sorted:对元素进行排序。例如,
stream.sorted()
表示按照自然顺序排序,stream.sorted(Comparator.comparing(String::length))
表示按照长度排序。 - limit:截取前n个元素。例如,
stream.limit(10)
表示只保留前10个元素。 - skip:跳过前n个元素。例如,
stream.skip(10)
表示跳过前10个元素。
终端操作
- forEach:对每个元素执行一个操作。例如,
stream.forEach(System.out::println)
表示打印每个元素。 - count:返回元素的个数。例如,
stream.count()
表示返回流中元素的个数。 - collect:将流转换成其他形式。例如,
stream.collect(Collectors.toList())
表示将流转换成列表,stream.collect(Collectors.joining(","))
表示将流中的字符串用逗号连接起来。 - reduce:对流中的元素进行归约操作,生成一个值。例如,
stream.reduce((s1, s2) -> s1 + s2)
表示将流中的字符串拼接起来,stream.reduce(0, (n1, n2) -> n1 + n2)
表示将流中的整数求和。 - min:返回最小的元素。例如,
stream.min(Comparator.comparing(String::length))
表示返回长度最短的字符串。 - max:返回最大的元素。例如,
stream.max(Comparator.comparing(String::length))
表示返回长度最长的字符串。 - anyMatch:判断是否有任意一个元素满足条件。例如,
stream.anyMatch(s -> s.startsWith("a"))
表示判断是否有以a开头的字符串。 - allMatch:判断是否所有的元素都满足条件。例如,
stream.allMatch(s -> s.length() > 3)
表示判断是否所有的字符串长度都大于3。 - noneMatch:判断是否没有任何一个元素满足条件。例如,
stream.noneMatch(s -> s.contains("z"))
表示判断是否没有包含z的字符串。
如何使用并行流?
并行流是指可以利用多核 CPU 并行处理的流。并行流可以提高性能,但也有一些注意事项:
- 并行流不一定比串行流快,因为并行流需要额外的线程切换和数据同步开销。
- 并行流不一定能保证顺序性,因为多个线程同时处理元素可能会导致乱序。
- 并行流不适合有状态的操作,因为多个线程同时操作共享状态可能会导致数据不一致。
要创建一个并行流,有以下几种方式:
- 调用 parallelStream() 方法。例如,
list.parallelStream()
表示从列表创建一个并行流。 - 调用 parallel() 方法。例如,
stream.parallel()
表示把一个串行流转换成并行流。 - 使用 StreamSupport 类。例如,
StreamSupport.stream(iterable.spliterator(), true)
表示从可迭代对象创建一个并行流。
要把一个并行流转换成串行流,可以调用 sequential() 方法。例如,stream.sequential()
表示把一个并行流转换成串行流。
总结
本文介绍了 Java 8 流式编程的基本概念和常用方法,希望能帮助你理解和使用流式编程,让你的代码更优雅、更高效、更简洁。