Stream
Java 8 中一个主要的新功能是引入了流(Stream)功能。在java.util.stream
中包含用于处理元素序列的类。其中,最重要的类是Stream<T>
。下面我们就来看看如何使用现有的数据源创建流。
创建Stream
可以使用 stream()
和 of()
方法从不同的数据源(例如:集合、数组)创建流:
String[] arr = new String[]{"万", "猫", "学", "社"};
Stream<String> stream = Arrays.stream(arr);
stream = Stream.of("万", "猫", "学", "社");
Collection
接口新增了一个 stream()
默认方法,允许使用任何集合作为数据源来创建 Stream<T>
:
List<String> list = new ArrayList();
list.add("万");
list.add("猫");
list.add("学");
list.add("社");
Stream<String> stream = list.stream();
在多线程中使用Stream
Stream 还通过提供 parallelStream()
方法来简化多线程操作,该方法以并行模式运行对流元素的操作。
下面的代码可以对流的每个元素并行运行 doWork()
方法:
List<String> list = new ArrayList();
list.add("万");
list.add("猫");
list.add("学");
list.add("社");
list.parallelStream().forEach(element -> doWork(element));
接下来,我们将介绍一些基本的 Stream 操作。
Stream 操作
在流上可以执行许多有用的操作。它们被分为中间操作(返回 Stream<T>
)和终端操作(返回明确定义类型的结果),中间操作允许链接。
我需要注意的是,流上的操作不会改变数据源。
下面是一个简单的例子:
代码语言:javascript复制List<String> list = new ArrayList();
list.add("万");
list.add("猫");
list.add("学");
list.add("社");
long count = list.stream().distinct().count();
在上面的例子中,distinct()
方法表示一个中间操作,它创建了前一个流的唯一元素的新流。而 count()
方法是一个终端操作,它返回流的大小。
迭代
Stream 帮助我们替代了 for、for-each 和 while 循环。它可以让我们把精力集中在操作的逻辑上,而不是在迭代元素序列上。
比如下面的代码:
代码语言:javascript复制for (String string : list) {
if (string.contains("猫")) {
return true;
}
}
这段代码只需要一行 Stream 代码就可以实现:
代码语言:javascript复制boolean isExist = list.stream().anyMatch(element -> element.contains("猫"));
过滤
filter()
方法可以让我们选择满足谓词条件的元素流。
比如下面的代码:
代码语言:javascript复制List<String> list = new ArrayList();
list.add("万");
list.add("猫");
list.add("学");
list.add("社");
Stream<String> stream = list.stream().filter(element -> element.contains("猫"));
在上面的例子中,创建了一个 List<String>
的 Stream<String>
,查找该流中所有包含字符“猫”的元素,并创建一个只包含筛选后元素的新流。
映射
为了通过将特殊函数应用于流元素来转换它们,并将这些新元素收集到流中,我们可以使用 map()
方法。
比如下面的代码:
代码语言:javascript复制List<String> list = new ArrayList();
list.add("1");
list.add("2");
list.add("3");
Stream<Integer> stream = list.stream().map(str -> Integer.valueOf(str));
在上面的例子中,通过对初始流的每个元素应用特定的 lambda 表达式将 Stream<String>
转换为 Stream<Integer>
。
如果您有一个流,其中每个元素都包含其自己的元素序列,并且您想创建这些内部元素的流,则应使用 flatMap()
方法。
比如下面的代码:
代码语言:javascript复制public class Writer {
private String name;
private List<String> books;
}
代码语言:javascript复制List<Writer> writers = new ArrayList<>();
writers.add(new Writer());
Stream<String> stream = writers.stream().flatMap(writer -> writer.getBooks().stream());
在上面的例子中,我们有一个类型为 Writer
的元素列表。Writer
类包含一个类型为 List<String>
的字段 books
。使用 flatMap() 方法,字段 books
中的每个元素将被提取并添加到新的结果流中。之后,最开始的 Stream将会丢失。
匹配
Stream 提供了一组方便的工具,根据一些谓词验证一个序列的元素。我们可以使用以下方法之一:
anyMatch()
:只要有一个条件满足即返回trueallMatch()
:必须全部都满足才会返回truenoneMatch()
:全都不满足才会返回true
它们都返回 boolean 的终端操作。
比如下面的代码:
代码语言:javascript复制List<String> list = new ArrayList();
list.add("万");
list.add("猫");
list.add("学");
list.add("社");
list.stream().anyMatch(element -> element.contains("万")); // true
list.stream().allMatch(element -> element.contains("万")); // false
list.stream().noneMatch(element -> element.contains("万")); // false
对于空的 Stream,无论给定的谓词是什么,allMatch() 方法都将返回 true:
代码语言:javascript复制Stream.empty().anyMatch(Objects::nonNull); // false
这是一个合理的值,因为我们找不到不满足谓词的任何元素。
同样地,对于空的 Stream,anyMatch() 方法总是返回 false:
代码语言:javascript复制Stream.empty().anyMatch(Objects::nonNull); // false
同样地,这也是合理的,因为我们找不到满足这个条件的元素。
合并
我可以使用类型为 Stream 的 reduce()
方法,根据指定的函数将一系列元素合并为某个值。这个方法有两个参数:第一个是起始值,第二个是累加器函数。
比如下面的代码:
代码语言:javascript复制List<Integer> integers = Arrays.asList(1, 2, 3);
Integer reduced = integers.stream().reduce(4, (a, b) -> a b);
在上面的例子中,有一个 List<Integer>
,我们将这些元素加起来,并加上一个初始的整数(在这个例子中是4)。那么,运行以下代码的结果是10(4 1 2 3)。
收集
在 Stream 类型中,也可以通过 collect()
方法来进行收集。这个操作非常方便,可以将一个流转换为 Collection
或 Map
,也可以将一个流表示为单个字符串。Collectors
是一个实用类,提供了几乎所有典型的收集操作的解决方案。对于一些不太常见的任务,可以创建自定义的收集器。
下面的代码使用终端操作 collect()
将 Stream<String>
转换为 List<String>
。
List<String> resultList
= list.stream().map(element -> element.toUpperCase()).collect(Collectors.toList());
最后
Stream 的高级示例非常丰富,本文的目的是为了让我们快速了解 Stream 功能的用法,并启发我们继续探索和深入学习。
Stream 是 Java 8 中非常强大和实用的 API,它为开发人员提供了一种更加简便的方式来处理数据。希望我们通过本文的介绍和示例,可以快速上手使用 Stream,并继续深入学习和探索。