Stream是Java 8引入的革命性API,它彻底改变了我们处理集合数据的方式。想象一下,你面前有一条装配流水线,原材料(数据)从一端进入,经过各种加工站(操作),最后变成成品(结果)。这就是Stream的工作模式——它把数据处理抽象成一条可组装的操作链。
与传统的for循环相比,Stream有三大不可替代的优势:
声明式编程:你只需要告诉程序"做什么",而不是"怎么做"。比如用filter筛选数据时,你只需定义筛选条件,不用关心如何遍历集合。
链式调用:多个操作可以像拼积木一样连接起来,形成清晰的数据处理流水线。这种写法比嵌套的for/if结构更易读。
并行友好:只需将stream()改为parallelStream(),就能自动获得多线程处理能力,这对大数据量处理尤其重要。
实际开发中发现:对于超过10万条数据的集合处理,合理使用并行流可以使性能提升3-5倍。但要注意线程安全问题,后面会详细说明。
Stream本身不存储任何数据,它只是数据源的"视图"。这个特性带来两个重要影响:
内存效率:处理大型数据集时,Stream不会一次性将所有数据加载到内存,而是按需处理。
实时数据:如果原始数据源被修改(虽然不推荐这么做),Stream操作会反映这些变化。
java复制List<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3));
Stream<Integer> stream = numbers.stream();
numbers.add(4); // 修改原始集合
stream.forEach(System.out::print); // 输出: 1234
所有中间操作都返回新Stream,原始数据源始终保持不变。这个特性使得:
这是Stream最精妙的设计之一。中间操作只是"记下"要做什么,直到遇到终止操作才会真正执行。这种设计带来了显著的性能优化:
findFirst()找到结果后,后续元素就不会处理java复制// 下面这段代码实际上只遍历了一次
List<String> result = list.stream()
.filter(s -> {
System.out.println("filter: " + s);
return s.length() > 3;
})
.map(s -> {
System.out.println("map: " + s);
return s.toUpperCase();
})
.collect(Collectors.toList());
Stream就像一次性使用的迭代器,一旦被消费就不能重复使用。这个限制源于:
如果需要重复处理,应该每次都从数据源新建Stream:
java复制Supplier<Stream<String>> streamSupplier = () -> list.stream();
// 每次get()都获得新的Stream
streamSupplier.get().forEach(...);
streamSupplier.get().filter(...);
java复制// 串行流 - 单线程顺序处理
Stream<String> stream = list.stream();
// 并行流 - 多线程并发处理
Stream<String> parallelStream = list.parallelStream();
选择建议:
java复制int[] intArray = {1, 2, 3};
IntStream intStream = Arrays.stream(intArray);
String[] strArray = {"a", "b"};
Stream<String> strStream = Arrays.stream(strArray);
与Stream.of()的区别:
java复制int[] array = {1, 2, 3};
Stream<int[]> stream1 = Stream.of(array); // 整个数组作为一个元素
IntStream stream2 = Arrays.stream(array); // 数组元素展开
java复制Stream<Integer> numbers = Stream.of(1, 2, 3);
Stream<String> strings = Stream.of("a", "b", null); // 允许null但危险
Stream<Object> mixed = Stream.of("a", 1, true); // 混合类型
注意事项:
filter(Objects::nonNull)1. generate() - 随机数据生成器
java复制// 生成10个随机数
Stream.generate(Math::random)
.limit(10)
.forEach(System.out::println);
2. iterate() - 有规律序列
java复制// Java 8: 无限序列
Stream.iterate(0, n -> n + 1)
.limit(10)
.forEach(System.out::print); // 0123456789
// Java 9+: 带终止条件
Stream.iterate(0, n -> n < 100, n -> n + 1)
.forEach(System.out::print); // 0到99
实际应用场景:
java复制// 筛选偶数
Stream.of(1, 2, 3, 4)
.filter(n -> n % 2 == 0)
.forEach(System.out::print); // 24
性能提示:
java复制// 字符串转大写
Stream.of("a", "b")
.map(String::toUpperCase)
.forEach(System.out::print); // AB
// 对象属性提取
persons.stream()
.map(Person::getName)
.collect(Collectors.toList());
常见陷阱:
java复制List<List<Integer>> nestedList = Arrays.asList(
Arrays.asList(1, 2),
Arrays.asList(3, 4)
);
List<Integer> flatList = nestedList.stream()
.flatMap(List::stream)
.collect(Collectors.toList()); // [1, 2, 3, 4]
典型应用场景:
distinct去重:
java复制Stream.of(1, 2, 1, 3)
.distinct()
.forEach(System.out::print); // 123
sorted排序:
java复制// 自然排序
Stream.of(3, 1, 2)
.sorted()
.forEach(System.out::print); // 123
// 自定义排序
persons.stream()
.sorted(Comparator.comparing(Person::getAge).reversed())
.collect(Collectors.toList());
性能注意:
unordered()提示基本收集:
java复制List<String> list = stream.collect(Collectors.toList());
Set<String> set = stream.collect(Collectors.toSet());
高级收集:
java复制// 转为Map
Map<Long, Person> personMap = persons.stream()
.collect(Collectors.toMap(Person::getId, Function.identity()));
// 分组
Map<String, List<Person>> peopleByCity = persons.stream()
.collect(Collectors.groupingBy(Person::getCity));
// 分区
Map<Boolean, List<Person>> partitioned = persons.stream()
.collect(Collectors.partitioningBy(p -> p.getAge() > 18));
java复制// 求和
int sum = Stream.of(1, 2, 3)
.reduce(0, Integer::sum); // 6
// 字符串连接
String concat = Stream.of("a", "b", "c")
.reduce("", String::concat); // "abc"
// 找最大值
Optional<Integer> max = Stream.of(1, 2, 3)
.reduce(Integer::max); // Optional[3]
使用建议:
java复制// 是否存在成年人
boolean hasAdult = persons.stream()
.anyMatch(p -> p.getAge() >= 18);
// 是否全部是成年人
boolean allAdult = persons.stream()
.allMatch(p -> p.getAge() >= 18);
// 是否没有未成年人
boolean noChild = persons.stream()
.noneMatch(p -> p.getAge() < 18);
短路特性:
陷阱1:重复使用流
java复制Stream<String> stream = list.stream();
stream.forEach(...);
stream.filter(...); // 抛出IllegalStateException
解决:每次需要时重新创建流
陷阱2:并行流中的状态共享
java复制List<String> result = Collections.synchronizedList(new ArrayList<>());
list.parallelStream()
.filter(s -> s.length() > 3)
.forEach(result::add); // 线程安全但性能差
解决:使用collect代替
java复制List<String> result = list.parallelStream()
.filter(s -> s.length() > 3)
.collect(Collectors.toList());
使用peek查看流水线状态:
java复制List<String> result = list.stream()
.peek(s -> System.out.println("原始: " + s))
.filter(s -> s.length() > 3)
.peek(s -> System.out.println("过滤后: " + s))
.map(String::toUpperCase)
.peek(s -> System.out.println("映射后: " + s))
.collect(Collectors.toList());
记录执行时间:
java复制long start = System.nanoTime();
list.stream().count();
long duration = System.nanoTime() - start;
当内置收集器不能满足需求时,可以自定义:
java复制Collector<String, ?, Map<Boolean, List<String>>> partitionByLength =
Collector.of(
() -> new HashMap<Boolean, List<String>>() {{
put(true, new ArrayList<>());
put(false, new ArrayList<>());
}},
(map, str) -> map.get(str.length() > 5).add(str),
(map1, map2) -> {
map1.get(true).addAll(map2.get(true));
map1.get(false).addAll(map2.get(false));
return map1;
},
Collector.Characteristics.CONCURRENT
);
Map<Boolean, List<String>> result = words.stream()
.collect(partitionByLength);
高效读取大文件:
java复制try (Stream<String> lines = Files.lines(Paths.get("data.txt"))) {
long count = lines
.filter(line -> !line.isEmpty())
.count();
}
优雅处理可能抛出异常的操作:
java复制List<Integer> numbers = Stream.of("1", "2", "abc")
.map(s -> {
try {
return Integer.parseInt(s);
} catch (NumberFormatException e) {
return null;
}
})
.filter(Objects::nonNull)
.collect(Collectors.toList()); // [1, 2]
随着Java版本更新,Stream API也在不断进化:
Java 9新增:
takeWhile/dropWhile:条件截取ofNullable:创建可能为null的单元素流iterate增强:支持终止条件Java 16新增:
toList()直接方法:替代Collectors.toList()在实际项目中,Stream已经成为了集合处理的标配。从我参与的大型项目经验来看,合理使用Stream可以:
但切记:不是所有场景都适合Stream。简单遍历使用for循环可能更直接,特别是在需要复杂控制流或异常处理时。