一、Stream流的概念
Stream流是一种顺序的元素集合,它允许你以声明式方式处理集合数据,使代码更简洁、更易读。Stream 流不是一个数据结构,而是对数据源(如集合、数组等)进行一系列聚合操作的工具。
流的特点
- 不存储数据:流只是在数据源上进行操作,不存储元素。
- 函数式编程:流的操作不会修改数据源,而是返回一个新的流。
- 延迟执行:中间操作(如 filter、map)总是延迟执行,直到终端操作(如 collect、forEach)被调用。
- 可消费性:流只能被消费一次,消费后需要重新创建。
流的创建
你可以通过多种方式创建流:
import java.util.*;
import java.util.stream.*;
public class StreamExample {
public static void main(String[] args) {
// 1. 从集合创建
List<String> list = Arrays.asList("apple", "banana", "cherry");
Stream<String> streamFromList = list.stream();
// 2. 从数组创建
String[] array = {"apple", "banana", "cherry"};
Stream<String> streamFromArray = Arrays.stream(array);
// 3. 使用Stream.of()
Stream<String> streamOf = Stream.of("apple", "banana", "cherry");
// 4. 创建空流
Stream<String> emptyStream = Stream.empty();
// 5. 创建无限流(通过生成器或迭代器)
Stream<Integer> infiniteStream = Stream.generate(() -> 1); // 生成无限个1
Stream<Integer> iterateStream = Stream.iterate(0, n -> n + 2); // 生成偶数序列
}
}
流的操作
Java中的Stream流主要包含两种类型:中间操作和终端操作。中间操作用于对流进行一系列的转换和操作,而终端操作用于从流中获取结果。
中间操作
中间操作返回一个新的流,允许你链式调用多个操作。常见的中间操作有:
import java.util.*;
import java.util.stream.*;
public class StreamIntermediateOperations {
public static void main(String[] args) {
List<String> fruits = Arrays.asList("apple", "banana", "cherry", "date", "elderberry");
// 1. filter:过滤元素
Stream<String> filteredStream = fruits.stream()
.filter(fruit -> fruit.startsWith("a"));
// 2. map:转换元素
Stream<Integer> lengthStream = fruits.stream()
.map(String::length);
// 3. distinct:去重
Stream<String> distinctStream = Stream.of("apple", "apple", "banana")
.distinct();
// 4. sorted:排序
Stream<String> sortedStream = fruits.stream()
.sorted();
// 5. limit:限制元素数量
Stream<String> limitedStream = fruits.stream()
.limit(3);
// 6. skip:跳过元素
Stream<String> skippedStream = fruits.stream()
.skip(2);
// 7. flatMap:将流中的每个元素转换为另一个流并合并
Stream<String> words = Stream.of("Hello World", "Java Stream")
.flatMap(s -> Arrays.stream(s.split(" ")));
}
}
终端操作
终端操作会触发流的执行并产生结果。常见的终端操作有:
import java.util.*;
import java.util.stream.*;
public class StreamTerminalOperations {
public static void main(String[] args) {
List<String> fruits = Arrays.asList("apple", "banana", "cherry", "date", "elderberry");
// 1. forEach:遍历元素
fruits.stream()
.forEach(System.out::println);
// 2. collect:收集元素到集合
List<String> filteredFruits = fruits.stream()
.filter(fruit -> fruit.length() > 5)
.collect(Collectors.toList());
// 3. toArray:转换为数组
String[] fruitArray = fruits.stream()
.toArray(String[]::new);
// 4. reduce:归约操作,将元素组合成一个值
Optional<String> concatenated = fruits.stream()
.reduce((a, b) -> a + ", " + b);
// 5. count:统计元素数量
long count = fruits.stream()
.count();
// 6. anyMatch、allMatch、noneMatch:匹配检查
boolean anyStartsWithA = fruits.stream()
.anyMatch(fruit -> fruit.startsWith("a"));
boolean allStartsWithA = fruits.stream()
.allMatch(fruit -> fruit.startsWith("a"));
boolean noneStartsWithZ = fruits.stream()
.noneMatch(fruit -> fruit.startsWith("z"));
// 7. findFirst、findAny:查找元素
Optional<String> first = fruits.stream()
.findFirst();
Optional<String> any = fruits.stream()
.findAny();
}
}
流的使用示例
List<String> list = Arrays.asList("apple", "banana", "cherry");
// List 转 Map
Map<Integer, String> listToMap = list.stream()
//Collectors.toMap是一个终端操作,将流中的元素收集到一个Map中
.collect(Collectors.toMap(
String::length, // 键:字符串长度,"cherry".length() → 6(与 "banana" 冲突)
s -> s, // 值:字符串本身
(existing, replacement) -> existing // 处理键冲突的策略
));
System.out.println("List 转 Map: " + listToMap);
执行流程:
list.stream()
- 将List转换为Stream,为后续的流式操作做准备。
.collect(Collectors.toMap(...))
- Collectors.toMap是一个终端操作,将流中的元素收集到一个Map中。
- 该方法接收三个核心参数:
String::length
- 使用方法引用,将字符串的长度作为 Map 的键。
- 例如:"apple" 的长度为 5,"banana" 的长度为 6。
s -> s
- 使用 Lambda 表达式,直接将字符串本身作为 Map 的值。
- 等效于Function.identity()。
(existing, replacement) -> existing
- 当两个不同的字符串具有相同长度时(如 "apple" 和 "cherry" 均为 5),保留先出现的元素(existing),丢弃后出现的元素(replacement)。
- 若改为(e, r) -> r,则保留后出现的元素。
处理"cherry"时:
- 键:"cherry".length() → 6(与 "banana" 冲突)
- 值:"cherry"
- 冲突策略:保留existing("banana"),丢弃replacement("cherry")
- 最终 Map:{5=apple, 6=banana}
输出:
