“【曾续缘聊技术】专栏持续更新中!我是深耕计算机领域的曾续缘,专注用通俗语言讲透硬核知识。关注+星标,获取最新技术干货推送!”
Java Stream API 是 Java 8 中引入的一个强大特性,它提供了一种高效且易于理解的数据处理方式。在处理集合时,Stream API 允许我们以声明式的方式表达复杂的数据处理操作,从而简化代码并提高效率。
stream()
方法来创建一个流。List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream();
Arrays.stream(Object[])
方法。int[] array = {1, 2, 3};
IntStream stream = Arrays.stream(array);
Stream.of(T... values)
方法。Stream<String> stream = Stream.of("a", "b", "c");
中间操作(Intermediate Operations)返回的是一个新的流,因此可以将其链接起来,形成一个操作链。这些操作不会立即执行,只有当终端操作被调用时,整个操作链才会开始执行。
例如,下面的代码链包含了两个中间操作map
和filter
,它们将转换元素为大写,并过滤出以"A"开头的字符串:
Stream<String> stream = list.stream()
.map(String::toUpperCase)
.filter(s -> s.startsWith("A"));
在这个例子中,直到调用终端操作之前,中间操作不会执行任何操作。
中间操作分为无状态操作和有状态操作,这两类操作在处理元素时的行为和性能特征有所不同。
无状态操作(Stateless Operations)不维护任何状态信息,它们处理每个元素时都是独立的,不需要考虑其他元素的状态。这意味着这些操作可以并行执行,而不会影响结果。常见的无状态操作包括:
例如,map
操作将每个元素应用一个函数,而不会影响其他元素:
Stream<String> upperCase = list.stream().map(String::toUpperCase);
在这个例子中,每个元素都被独立地转换为大写,这个操作不需要知道其他元素的状态。
有状态操作(Stateful Operations)在处理元素时会维护一个或多个状态,这些状态可能会影响其他元素的处理。这意味着这些操作通常不能并行执行,或者需要特殊的处理来保证并行执行的正确性。常见的有状态操作包括:
例如,sorted
操作会对流中的所有元素进行排序,这需要知道所有元素的比较关系:
Stream<String> sorted = list.stream().sorted();
在这个例子中,为了对元素进行排序,sorted
操作需要收集所有元素并比较它们,这是一个有状态的操作。
终端操作(Terminal Operations)会触发流的处理,并返回一个结果或产生副作用。终端操作会遍历流中的元素,并对它们应用中间操作链中定义的处理。终端操作执行后,流就被消耗掉了,不能再次使用。终端操作包括以下几种类型:
forEach
,用于遍历每个元素并执行一个操作。collect
,用于将流中的元素收集到一个集合中。reduce
,用于将流中的所有元素规约为一个结果。findFirst
、findAny
,用于查找流中的元素。anyMatch
、allMatch
、noneMatch
,用于检查流中的元素是否满足某个条件。例如,下面的代码使用了forEach
作为终端操作,它会遍历流中的每个元素,并打印出来:
stream.forEach(System.out::println);
在这个例子中,forEach
操作会立即执行,并且流中的元素会按照中间操作链定义的方式被处理和打印。
Java Stream的流水线设计允许将多个操作链接起来,形成一个操作链。每个操作都返回一个新的流,这样可以方便地将多个操作组合起来,而不需要创建临时集合。流水线使得Stream API易于使用且表达性强。
例如,下面的代码将多个中间操作和终端操作链接起来,形成一个流水线:
list.stream()
.filter(s -> s.startsWith("A"))
.map(String::toUpperCase)
.sorted()
.forEach(System.out::println);
在这个例子中,filter
、map
、sorted
和forEach
操作被链接起来,形成了一个操作链。
Java Stream的中间操作是惰性求值的,这意味着它们不会立即执行。相反,它们会创建一个描述操作的流水线,只有当终端操作被调用时,这个流水线才会被实际执行。这种设计允许对整个操作链进行优化。
例如,下面的代码只创建了操作链,并没有执行任何操作:
Stream<String> stream = list.stream()
.filter(s -> s.startsWith("A"))
.map(String::toUpperCase);
直到调用终端操作,比如forEach
,上面的操作链才会被执行:
stream.forEach(System.out::println);
并行流可以将顺序流转换为并行流,从而可能提高某些操作的执行效率。并行流利用多核处理器的并发能力,通过将任务分散到多个线程上执行,来加速数据处理。
使用parallel()
方法可以将顺序流转换为并行流:
Stream<String> parallelStream = list.stream().parallel();
在Java Stream API的高级应用中,我们会遇到一些更复杂的数据处理场景,需要使用flatMap
和reduce
等操作来处理。同时,为了提高性能,我们需要遵循一些最佳实践,比如避免在中间操作中修改共享变量,并选择合适的流类型(并行流或顺序流)。
flatMap
进行扁平化操作flatMap
方法用于将多个流合并成一个流。当你有一个包含多个集合的流,并且想要将这些集合中的元素合并到一个流中时,flatMap
非常有用。
例如,假设我们有一个单词列表,每个单词都是一个字符串流,我们想要将这些单词的字符扁平化到一个流中:
List<String> words = Arrays.asList("hello", "world");
Stream<String> letters = words.stream()
.flatMap(word -> Stream.of(word.split("")));
reduce
进行累加操作reduce
方法用于将流中的所有元素合并成一个结果。这通常用于求和、求乘积或其他类型的累加操作。
例如,计算一个整数列表的所有元素的和:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream().reduce(0, Integer::sum);
参考链接:https://cengxuyuan.cn