什么是Stream流
Stream流是 Java8 引入的一个的新的API,用于以声明式编程风格高效处理集合、数组或I/O资源等数据。通过链式操作的方式对数据进行计算,最终产生结果。
为什么要引入Stream流API
我们在使用Stream流之前操作集合的时候,可能会存在部分逻辑导致多层循环嵌套,这样会导致代码的可读性变差、代码冗余度变高。同时,如果逻辑中存在需要进行并行计算的地方,并行的复杂度比较大,手动去管理多线程可能会出现问题。
因此,Java8引入了Stream流这个API,用于简化传统的集合操作,通过声明式编程范式和函数式编程思想快速进行数据集合的处理。
题外话:声明式编程和命令式编程
-
命令式编程的核心思想在于开发者通过编写具体的步骤指令告诉计算机该如何做。
-
声明式编程的核心思想在于开发者通过描述做什么,而非具体步骤,让底层框架自动处理如何做。
命令式编程关注于控制流程、强调状态的变化,而声明式编程的重点关注于结果目标而不在乎中间细节,强调没有副作用。Stream流就是声明式编程的典型示例,抽象层级高,通过组合操作处理数据流,代码简洁,可维护性强。
如何创建Stream流
一、从集合创建
通过 Collection.steram() 或 Collection.parallelStream() 创建顺序流或者并行流。
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream(); // 顺序流
Stream<String> parallelStream = list.parallelStream(); // 并行流
二、从数组创建
通过 Arrays.stream(T[] arr) 创建数组的stream流。
String[] array = {"Java", "Stream", "API"};
Stream<String> stream = Arrays.stream(array);
// 处理原始类型数组(避免装箱)
int[] intArray = {1, 2, 3};
IntStream intStream = Arrays.stream(intArray);
三、直接用数据创建
通过 Stream.of(T... values)。
Stream<String> stream = Stream.of("A", "B", "C");
Stream<Integer> numberStream = Stream.of(1, 2, 3);
四、生成连续数据序列流
// 生成1~5(不包含5)的整数流
IntStream.range(1, 5).forEach(System.out::print); // 输出:1234
// 生成1~5(包含5)的整数流
IntStream.rangeClosed(1, 5).forEach(System.out::print); // 输出:12345
Stream流的核心操作
Stream流的核心操作分为中间操作和终端操作,下面进行分别介绍。
一、中间操作
过滤(Filter)
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> evens = numbers.stream()
.filter(n -> n % 2 == 0) // 过滤偶数
.collect(Collectors.toList()); // [2,4]
映射(Map)
List<String> words = Arrays.asList("apple", "banana");
List<Integer> lengths = words.stream()
.map(String::length) // 映射为单词长度
.collect(Collectors.toList()); // [5, 6]
扁平化映射(FlatMap)
List<List<String>> nestedList = Arrays.asList(
Arrays.asList("a", "b"),
Arrays.asList("c", "d")
);
List<String> flatList = nestedList.stream()
.flatMap(Collection::stream) // 展开为 "a","b","c","d"
.collect(Collectors.toList());
去重(Distinct)
List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3);
List<Integer> unique = numbers.stream()
.distinct()
.collect(Collectors.toList()); // [1,2,3]
排序(Sorted)
List<String> names = Arrays.asList("Bob", "Alice", "Charlie");
List<String> sorted = names.stream()
.sorted() // 自然排序:["Alice", "Bob", "Charlie"]
.collect(Collectors.toList());
分页(Limit)
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> subList = numbers.stream()
.skip(2) // 跳过前2个元素:[3,4,5]
.limit(2) // 取前2个元素:[3,4]
.collect(Collectors.toList());
二、终端操作
遍历(ForEach)
List<String> names = Arrays.asList("Alice", "Bob");
names.stream().forEach(System.out::println); // 输出每个名字
收集(Collect)
// 转为List/Set
List<Integer> list = stream.collect(Collectors.toList());
Set<Integer> set = stream.collect(Collectors.toSet());
// 转为Map
Map<Integer, String> map = users.stream()
.collect(Collectors.toMap(User::getId, User::getName));
// 分组统计
Map<String, List<User>> groupByCity = users.stream()
.collect(Collectors.groupingBy(User::getCity));
// 拼接字符串
String joined = stream.collect(Collectors.joining(", "));
归约(Reduce)
int sum = numbers.stream()
.reduce(0, Integer::sum); // 初始值0,累加所有元素
Optional<Integer> max = numbers.stream()
.reduce(Integer::max); // 无初始值,返回Optional
匹配(Match)
boolean hasEven = numbers.stream().anyMatch(n -> n % 2 == 0); // 是否存在偶数
boolean allPositive = numbers.stream().allMatch(n -> n > 0); // 是否全为正数
查找(Find)
Optional<Integer> first = numbers.stream().findFirst();
Optional<Integer> any = numbers.parallelStream().findAny(); // 并行时可能随机返回
统计(Count/Min/Max)
long count = numbers.stream().count();
Optional<Integer> min = numbers.stream().min(Integer::compareTo);
Optional<Integer> max = numbers.stream().max(Integer::compareTo);
Stream流高级特性
并行流
Stream的并行流底层基于Fork/Join框架,自动拆分任务到线程池中,可以进行并行计算。下面列出一个表格用于标识顺序流和并行流在一些场景下的性能对比,表格来源网络。
场景 | 数据量 | 顺序流耗时 | 并行流耗时 | 加速比 |
---|---|---|---|---|
简单过滤+统计 | 10万 | 12 ms | 8 ms | 1.5x |
复杂计算(如加密) | 10万 | 350 ms | 85 ms | 4.1x |
排序操作 | 10万 | 45 ms | 60 ms | 0.75x |
我们应该根据情况合理选用不同的流:当数据量小且操作简单的时候,我们应该优先选择顺序流;当数据量较大且操作复杂的时候,我们应该选用并行流。
总结
Java为我们提供了一种新的操作集合的方法,通过Stream流可以简化我们的编码,让我们代码的可读性和可维护性得到提升;同时,对于一些特殊情况下,Stream流也可以加快我们的计算速度。根据实际场景,选用适合的操作。