Java Stream 流详细使用指南
一、Stream 流概述
Stream 是 Java 8 引入的强大特性,它提供了一种函数式编程风格的方式来处理数据集合或数组。其核心在于将数据处理过程分解为一系列的操作步骤,这些操作可以方便地组合和复用。中间操作如过滤、映射等可以对数据进行转换和筛选,形成一个操作链,而终端操作则触发整个操作链的执行并产生最终结果。这种设计使得代码更加简洁、易读且易于维护,同时也能充分利用现代多核处理器的性能优势,通过并行流实现高效的并行处理。
二、创建 Stream 流
-
从集合创建
- 对于常见的 List、Set 等集合类型,使用
stream()方法能够轻松创建一个顺序流。例如:
- 对于常见的 List、Set 等集合类型,使用
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> numberStream = numbers.stream();
// 此时 numberStream 为包含 1, 2, 3, 4, 5 的顺序流,元素按集合中的顺序排列
- 若要利用多核处理器的并行能力,可以调用
parallelStream()方法创建并行流。例如:
Stream<Integer> parallelNumberStream = numbers.parallelStream();
// parallelNumberStream 为并行流,其内部元素 1, 2, 3, 4, 5 可能会在不同线程中被处理,处理顺序不固定
-
从数组创建
- 使用
Arrays.stream()方法可从数组生成 Stream。以整型数组为例:
- 使用
int[] intArray = {1, 2, 3, 4, 5};
IntStream intStream = Arrays.stream(intArray);
// intStream 包含数组 intArray 中的元素 1, 2, 3, 4, 5,且元素顺序与数组一致
-
通过 Stream.of () 方法创建
- 此方法允许直接将一组元素转换为 Stream。例如:
Stream<String> stringStream = Stream.of("apple", "banana", "cherry");
// stringStream 包含 "apple", "banana", "cherry" 三个字符串元素,顺序与传入顺序相同
三、中间操作
-
过滤(filter)
- 该操作依据指定的条件筛选出符合要求的元素。比如,从一个整数列表中找出所有偶数:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> evenNumbers = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
// evenNumbers 结果为 [2, 4],因为只有 2 和 4 满足 n % 2 == 0 的条件
-
这里的
n -> n % 2 == 0是一个 Lambda 表达式,它简洁地定义了筛选偶数的条件,即元素除以 2 的余数为 0。
-
映射(map)
- 主要用于将一种类型的元素转换为另一种类型。例如,将一个字符串列表中的所有字符串转换为大写形式:
List<String> words = Arrays.asList("hello", "world");
List<String> upperCaseWords = words.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
// upperCaseWords 结果为 ["HELLO", "WORLD"],通过 String::toUpperCase 方法引用将每个字符串转换为大写
-
这里的
String::toUpperCase是方法引用,等同于word -> word.toUpperCase()的 Lambda 表达式,用于对每个字符串元素执行大写转换操作。
-
排序(sorted)
- 可以对 Stream 中的元素进行排序。若对一个整数列表进行升序排序:
List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5);
List<Integer> sortedNumbers = numbers.stream()
.sorted()
.collect(Collectors.toList());
// sortedNumbers 结果为 [1, 1, 2, 3, 3, 4, 5, 5, 5, 6, 9],按照数字的自然顺序从小到大排序
- 如需自定义排序规则,可通过自定义比较器实现。例如,对一个字符串列表按照字符串长度进行排序:
List<String> words = Arrays.asList("apple", "banana", "cherry", "date");
List<String> sortedByLengthWords = words.stream()
.sorted(Comparator.comparing(String::length))
.collect(Collectors.toList());
// sortedByLengthWords 结果为 ["date", "apple", "cherry", "banana"],先按字符串长度升序排序,长度相同则保持原有顺序
四、终端操作
-
收集(collect)
- 用于将 Stream 中的元素收集到特定的数据结构中。如前面示例中,
Collectors.toList()将处理后的元素收集到一个列表。还可以使用其他收集器,例如Collectors.toSet()收集到集合中:
- 用于将 Stream 中的元素收集到特定的数据结构中。如前面示例中,
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 3);
Set<Integer> numberSet = numbers.stream()
.collect(Collectors.toSet());
// numberSet 结果为 [1, 2, 3],去除了列表中的重复元素,因为集合不允许重复
Collectors.toMap()则可收集到一个映射中。例如,将一个字符串列表转换为以字符串长度为键,字符串列表为值的映射:
List<String> words = Arrays.asList("apple", "banana", "cherry", "date");
Map<Integer, List<String>> wordMap = words.stream()
.collect(Collectors.groupingBy(String::length));
// wordMap 结果为 {5=[apple, cherry], 6=[banana], 4=[date]},按字符串长度分组
-
匹配(match)
- 包含
anyMatch、allMatch和noneMatch三种操作。 anyMatch用于检查 Stream 中是否至少存在一个元素满足给定条件。例如,检查一个整数列表中是否至少有一个偶数:
- 包含
List<Integer> numbers = Arrays.asList(1, 3, 5, 7, 9);
boolean anyEven = numbers.stream().anyMatch(n -> n % 2 == 0);
// anyEven 结果为 false,因为列表中没有偶数,均不满足 n % 2 == 0 的条件
allMatch用于判断 Stream 中的所有元素是否都满足条件。例如,检查一个整数列表中的所有元素是否都大于 0:
List<Integer> positiveNumbers = Arrays.asList(1, 2, 3, 4, 5);
boolean allPositive = positiveNumbers.stream().allMatch(n -> n > 0);
// allPositive 结果为 true,列表中所有元素都大于 0,满足条件
noneMatch用于检查 Stream 中是否没有元素满足给定条件。例如,检查一个整数列表中是否没有负数:
List<Integer> nonNegativeNumbers = Arrays.asList(0, 1, 2, 3, 4);
boolean noneNegative = nonNegativeNumbers.stream().noneMatch(n -> n < 0);
// noneNegative 结果为 true,列表中不存在负数,满足条件
-
计数(count)
- 用于统计 Stream 中的元素数量。例如,计算一个整数列表中的元素个数:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
long count = numbers.stream().count();
// count 结果为 5,列表中共有 5 个整数元素
五、并行流注意事项
-
线程安全问题
- 当使用并行流时,由于多个线程可能同时访问和修改共享数据,容易引发线程安全问题。例如,对一个共享变量进行累加操作:
class Counter {
private int count = 0;
public void increment() {
count++;
}
public int getCount() {
return count;
}
}
Counter counter = new Counter();
IntStream.range(0, 1000).parallel().forEach(i -> counter.increment());
System.out.println(counter.getCount());
// 可能得到的结果不是 1000,因为多个线程同时访问和修改 count 变量,可能出现数据不一致的情况
- 为解决此问题,可以使用
Atomic类(如AtomicInteger)。修改上述代码如下:
class AtomicCounter {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.getAndIncrement();
}
public int getCount() {
return count.get();
}
}
AtomicCounter atomicCounter = new AtomicCounter();
IntStream.range(0, 1000).parallel().forEach(i -> atomicCounter.increment());
System.out.println(atomicCounter.getCount());
// 结果将正确地为 1000,AtomicInteger 保证了原子性操作,避免了线程安全问题
-
或者使用同步机制(如
synchronized块)来确保共享变量的操作安全,但同步机制可能会带来一定的性能开销。
-
性能考虑
- 并行流并非在所有情况下都比顺序流效率高。在数据量较小或者处理操作简单的场景下,并行流的线程创建、同步以及任务分配等开销可能会导致性能下降。例如:
List<Integer> smallNumbers = Arrays.asList(1, 2, 3, 4, 5);
long sequentialTime = System.nanoTime();
smallNumbers.stream().forEach(System.out::println);
long sequentialDuration = System.nanoTime() - sequentialTime;
long parallelTime = System.nanoTime();
smallNumbers.parallelStream().forEach(System.out::println);
long parallelDuration = System.nanoTime() - parallelTime;
System.out.println("顺序流执行时间: " + sequentialDuration + " 纳秒");
System.out.println("并行流执行时间: " + parallelDuration + " 纳秒");
// 通常情况下,顺序流执行时间可能更短,因为并行流的开销相对较大
- 只有当数据量较大且处理操作较为复杂,如对大规模数据进行复杂的计算或转换时,并行流才更有可能充分发挥多核处理器的优势,提高处理效率。例如,对一个包含大量元素的数组进行复杂的数学运算并求和:
int[] largeArray = new int[1000000];
Arrays.parallelSetAll(largeArray, i -> i);
long parallelSumTime = System.nanoTime();
int parallelSum = Arrays.stream(largeArray).parallel().sum();
long parallelSumDuration = System.nanoTime() - parallelSumTime;
long sequentialSumTime = System.nanoTime();
int sequentialSum = Arrays.stream(largeArray).sum();
long sequentialSumDuration = System.nanoTime() - sequentialSumTime;
System.out.println("顺序流求和时间: " + sequentialSumDuration + " 纳秒");
System.out.println("并行流求和时间: " + parallelSumDuration + " 纳秒");
// 此时并行流可能会比顺序流更快地完成求和操作,尤其是在多核处理器环境下