Java 8 流的简介
1. 概述
在本文中,我们将快速了解 Java 8 添加的主要新功能之一——流。 我们将解释什么是流,并通过简单的示例展示创建和基本流操作。
2. 流API
Java 8 的主要新特性之一是引入了流功能 - java.util.stream - 它包含用于处理元素序列的类。 中心 API 类是Stream<T> 。以下部分将演示如何使用现有的数据提供者源创建流。
2.1. 流创建
可以借助*stream()和of()*方法从不同的元素源创建流,例如集合或数组:
String[] arr = new String[]{"a", "b", "c"};
Stream<String> stream = Arrays.stream(arr);
stream = Stream.of("a", "b", "c");
将stream()默认方法添加到Collection接口并允许使用任何集合作为元素源来创建Stream<T>:
Stream<String> stream = list.stream();
2.2. 带流的多线程
Stream API 还通过提供以并行模式在流元素上运行操作的parallelStream()方法来简化多线程。 下面的代码允许为流的每个元素并行运行方法doWork():
list.parallelStream().forEach(element -> doWork(element));
在下一节中,我们将介绍一些基本的 Stream API 操作。
3. 流操作
可以对流执行许多有用的操作。
它们分为中间操作(返回Stream<T>)和终端操作(返回确定类型的结果)。中间操作允许链接。 还值得注意的是,对流的操作不会改变源。 这是一个简单的例子:
long count = list.stream().distinct().count();
因此,*distinct()*方法代表了一个中间操作,它创建了一个由前一个流的唯一元素组成的新流。*count()*方法是一个终端操作,它返回流的大小。
3.1. 迭代
Stream API 有助于替换for、for-each和while循环。它允许专注于操作的逻辑,但不能专注于元素序列的迭代。例如:
for (String string : list) {
if (string.contains("a")) {
return true;
}
}
只需一行 Java 8 代码即可更改此代码:
boolean isExist = list.stream().anyMatch(element -> element.contains("a"));
3.2. 过滤
*filter()*方法允许我们选择满足谓词的元素流。 例如,考虑以下列表:
ArrayList<String> list = new ArrayList<>();
list.add("One");
list.add("OneAndOnly");
list.add("Derek");
list.add("Change");
list.add("factory");
list.add("justBefore");
list.add("Italy");
list.add("Italy");
list.add("Thursday");
list.add("");
list.add("");
以下代码创建List<String> 的 Stream<String>,查找此流中包含*char “d”*的所有元素,并创建一个仅包含过滤元素的新流:
Stream<String> stream = list.stream().filter(element -> element.contains("d"));
3.3. 映射
要通过对其应用特殊函数来转换Stream的元素并将这些新元素收集到Stream中,我们可以使用*map()*方法:
List<String> uris = new ArrayList<>();
uris.add("C:\\My.txt");
Stream<Path> stream = uris.stream().map(uri -> Paths.get(uri));
因此,上面的代码通过将特定的 lambda 表达式应用于初始Stream的每个元素,将Stream<String>转换为Stream<Path>。 如果你有一个流,其中每个元素都包含它自己的元素序列,并且你想创建这些内部元素的流,你应该使用*flatMap()*方法:
List<Detail> details = new ArrayList<>();
details.add(new Detail());
Stream<String> stream
= details.stream().flatMap(detail -> detail.getParts().stream());
在这个例子中,我们有一个Detail类型的元素列表。Detail类包含一个字段PARTS,它是一个List<String>。在flatMap()方法的帮助下,字段PARTS中的每个元素都将被提取并添加到新的结果流中。之后,初始*Stream<Detail>*将丢失。
3.4. 匹配
Stream API 提供了一组方便的工具来根据某些谓词验证序列的元素。为此,可以使用以下方法之一:anyMatch()、allMatch()、noneMatch()。他们的名字是不言自明的。这些是返回boolean的终端操作:
boolean isValid = list.stream().anyMatch(element -> element.contains("h")); // true
boolean isValidOne = list.stream().allMatch(element -> element.contains("h")); // false
boolean isValidTwo = list.stream().noneMatch(element -> element.contains("h")); // false
对于空流,带有任何给定谓词的 allMatch() 方法将返回 true:
Stream.empty().allMatch(Objects::nonNull); // true
这是一个合理的默认设置,因为我们找不到任何不满足谓词的元素。 类似地, *anyMatch()*方法 对于空流 总是返回 false:
Stream.empty().anyMatch(Objects::nonNull); // false
同样,这是合理的,因为我们找不到满足这个条件的元素。
3.5. 减少
Stream API 允许在Stream类型的reduce()方法的帮助下,根据指定的函数将元素序列减少到某个值。这个方法有两个参数:第一个——起始值,第二个——一个累加器函数。 想象一下,你有一个List<Integer>并且你想要所有这些元素和一些初始Integer的总和(在这个例子中是 23)。因此,您可以运行以下代码,结果将是 26 (23 + 1 + 1 + 1)。
List<Integer> integers = Arrays.asList(1, 1, 1);
Integer reduced = integers.stream().reduce(23, (a, b) -> a + b);
3.6. 收集
减少也可以由Stream类型的collect()方法提供。在将流转换为Collection或Map并以单个字符串的形式表示流的情况下,此操作非常方便。有一个实用程序类Collectors,它为几乎所有典型的收集操作提供了解决方案。对于一些不重要的任务,可以创建自定义收集器。
List<String> resultList
= list.stream().map(element -> element.toUpperCase()).collect(Collectors.toList());
此代码使用终端collect()操作将Stream<String>简化为List<String>。