Stream API 是 Java 8 引入的一种用于处理集合数据的抽象,它代表了一组元素,可以是集合、数组或其它数据源中的元素。Stream API 允许开发者以一种类似于管道的方式对这些元素进行一系列的操作,如过滤、映射、排序、归约等,而无需显式地使用循环和临时变量
Stream 并不是数据结构,也不是线程安全的,它更像是对数据源(如集合、数组等)的某种查询接口。
List<String> fruits = Arrays.asList("Apple", "Banana", "Cherry", "Date");
fruits.stream()
.filter(fruit -> fruit.startsWith("A"))
.forEach(System.out::println); // 输出: Apple
Stream 核心特性
- 不可变性:Stream 本身是不可变的,即 Stream 中的元素不能被直接修改。如果需要对元素进行修改,通常需要通过映射等操作创建一个新的 Stream。
- 不可复用:一旦执行了终端操作,Stream 就会被消耗掉,不能再进行后续的操作。如果需要再次对相同的数据进行操作,需要重新获取 Stream。
- 延迟执行:Stream 的中间操作是延迟执行的,只有在终端操作触发时,整个流才会被执行
创建 Stream
Stream 可以从多种数据源创建,如集合、数组、I/O 通道等
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
public class StreamCreationExample {
public static void main(String[] args) {
// 从集合创建 Stream
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
Stream<String> streamFromList = names.stream();
// 从数组创建 Stream
String[] nameArray = {"David", "Eve", "Frank"};
Stream<String> streamFromArray = Arrays.stream(nameArray);
// 使用 Stream.of() 方法
Stream<String> streamOf = Stream.of("Grace", "Heidi", "Ivan");
// 打印所有流
streamFromList.forEach(System.out::println);
streamFromArray.forEach(System.out::println);
streamOf.forEach(System.out::println);
}
}
Stream 常用操作
Stream API 提供了丰富的操作,可以分为两类
- 中间操作(Intermediate Operations):这类操作会返回一个新的 Stream,并且可以在一个 Stream 上连续调用多个中间操作,形成操作链
- 终端操作(Terminal Operations):执行 Stream 操作链,并返回一个结果或产生某种副作用(如打印输出)。一旦执行了终端操作,Stream 就会被消耗掉,不能再进行后续的操作
中间操作
filter
根据指定条件过滤元素,保留满足条件的元素
List<String> fruits = Arrays.asList("Apple", "Banana", "Cherry", "Date");
fruits.stream()
.filter(fruit -> fruit.startsWith("A"))
.forEach(System.out::println); // 输出: Apple
map
将每个元素映射为另一种形式或类型
List<String> fruits = Arrays.asList("Apple", "Banana", "Cherry");
fruits.stream()
.map(String::toUpperCase)
.forEach(System.out::println);
// 输出: APPLE, BANANA, CHERRY
sorted
对元素进行排序,按自然顺序或指定的比较器顺序
List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5, 9);
numbers.stream()
.sorted()
.forEach(System.out::println);
// 输出: 1, 1, 3, 4, 5, 9
distinct
去除重复元素,保留唯一的元素
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 4, 4, 5);
numbers.stream()
.distinct()
.forEach(System.out::println);
// 输出: 1, 2, 3, 4, 5
limit
限制 Stream 的元素数量,只保留前 N 个元素
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
numbers.stream()
.limit(3)
.forEach(System.out::println);
// 输出: 1, 2, 3
skip
跳过前 N 个元素,保留剩余的元素
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
numbers.stream()
.skip(2)
.forEach(System.out::println);
// 输出: 3, 4, 5
终端操作
forEach
对 Stream 的每个元素执行指定的动作
List<String> fruits = Arrays.asList("Apple", "Banana", "Cherry");
fruits.stream()
.forEach(System.out::println);
// 输出: Apple, Banana, Cherry
collect
将 Stream 的元素收集到集合、列表、映射等容器中
import java.util.List;
import java.util.stream.Collectors;
List<String> fruits = Arrays.asList("Apple", "Banana", "Cherry");
List<String> filteredFruits = fruits.stream()
.filter(fruit -> fruit.startsWith("B"))
.collect(Collectors.toList());
System.out.println(filteredFruits); // 输出: [Banana]
在 Java 16 之前将 Stream 中的元素收集到一个 List 中时,需要使用 .collect(Collectors.toList())
从 Java 16 开始,Stream 接口本身新增了 toList() 方法,这样就可以直接使用该方法来将流中的元素收集到一个不可变的 List 里,代码会更加简洁
import java.util.List;
List<String> fruits = Arrays.asList("Apple", "Banana", "Cherry");
List<String> filteredFruits = fruits.stream()
.filter(fruit -> fruit.startsWith("B"))
.toList();
System.out.println(filteredFruits); // 输出: [Banana]
reduce
通过指定的操作将 Stream 的元素归约为单一的结果
import java.util.Optional;
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> sum = numbers.stream()
.reduce((a, b) -> a + b);
sum.ifPresent(System.out::println); // 输出: 15
count
计算 Stream 中元素的数量
long count = Stream.of("Apple", "Banana", "Cherry")
.count();
System.out.println(count); // 输出: 3
anyMatch, allMatch, noneMatch
判断 Stream 中是否存在满足特定条件的元素
List<String> fruits = Arrays.asList("Apple", "Banana", "Cherry");
// 是否存在以 'A' 开头的元素
boolean anyStartsWithA = fruits.stream()
.anyMatch(fruit -> fruit.startsWith("A"));
System.out.println(anyStartsWithA); // 输出: true
// 是否所有元素长度大于 5
boolean allLongerThan5 = fruits.stream()
.allMatch(fruit -> fruit.length() > 5);
System.out.println(allLongerThan5); // 输出: false
// 是否不存在包含 'Z' 的元素
boolean noneContainZ = fruits.stream()
.noneMatch(fruit -> fruit.contains("Z"));
System.out.println(noneContainZ); // 输出: true
findFirst, findAny
返回 Stream 中第一个元素或任意一个元素,通常与并行流结合使用
import java.util.Optional;
List<String> fruits = Arrays.asList("Apple", "Banana", "Cherry", "Date");
// 找到第一个以 'C' 开头的元素
Optional<String> firstC = fruits.stream()
.filter(fruit -> fruit.startsWith("C"))
.findFirst();
firstC.ifPresent(System.out::println); // 输出: Cherry
// 并行流中的 findAny
Optional<String> any = fruits.parallelStream()
.filter(fruit -> fruit.startsWith("B"))
.findAny();
any.ifPresent(System.out::println); // 输出: Banana
并行流
并行流是一种特殊的流,它可以将流中的元素分成多个块,每个块由不同的线程并行处理。Java 会自动管理线程的创建、调度和销毁,使得开发者可以更方便地利用多核处理器的计算能力,提高数据处理的效率
将普通流转换为并行流非常简单,只需要调用 parallelStream() 方法即可
import java.util.List;
import java.util.Random;
public class ParallelStreamPerformanceExample {
public static void main(String[] args) {
// 生成一百万个随机数
List<Integer> randomNumbers = new Random().ints(1, 100)
.limit(1_000_000)
.boxed()
.toList();
// 顺序流计算平方和
long start = System.currentTimeMillis();
long sumSequential = randomNumbers.stream()
.mapToLong(n -> n * n)
.sum();
long end = System.currentTimeMillis();
System.out.println("顺序流平方和: " + sumSequential + ",耗时: " + (end - start) + " ms");
// 并行流计算平方和
start = System.currentTimeMillis();
long sumParallel = randomNumbers.parallelStream()
.mapToLong(n -> n * n)
.sum();
end = System.currentTimeMillis();
System.out.println("并行流平方和: " + sumParallel + ",耗时: " + (end - start) + " ms");
}
}
输出结果
顺序流平方和: 3318884152,耗时: 5 ms
并行流平方和: 3318884152,耗时: 12 ms
并行流中的操作可能会在多个线程中同时执行,因此在处理共享资源时需要确保线程安全。如果在并行流中使用了非线程安全的对象,可能会导致数据不一致或其它并发问题
import java.util.ArrayList;
import java.util.List;
public class ParallelStreamThreadSafety {
public static void main(String[] args) {
List<Integer> numbers = List.of(1, 2, 3, 4, 5);
List<Integer> result = new ArrayList<>();
// 这是不安全的,因为 ArrayList 不是线程安全的
numbers.parallelStream()
.forEach(result::add);
System.out.println(result);
}
}
在上述代码中,ArrayList 不是线程安全的,使用并行流的 forEach 方法向其中添加元素可能会导致并发修改异常。可以使用线程安全的集合,如 CopyOnWriteArrayList 来避免这个问题
Optional 与 Stream
Optional 是 Java 8 引入的一个容器对象,用于防止出现 NullPointerException。它经常与 Stream 结合使用,特别是在处理可能为空的结果时
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// 查找第一个以 'D' 开头的名称
Optional<String> optionalName = names.stream()
.filter(name -> name.startsWith("D"))
.findFirst();
optionalName.ifPresent(name -> System.out.println("Found: " + name));
// 如果没有找到,则不会输出