在Java 8之前,集合处理通常采用传统的for循环或迭代器模式,代码冗长且意图不够清晰。Java 8引入的Stream API彻底改变了这一现状,让我们能够以声明式、函数式的方式处理数据集合。本文将带你深入探索Stream的强大功能,并通过实际代码示例展示其优雅之处。
Stream(流)是Java 8中处理集合(Collection)数据的高级抽象。它不是一个数据结构,而是一个来自数据源(集合、数组等)的元素队列,支持聚合操作和并行处理。
核心特点:
返回新的Stream,可链式调用
filter():过滤map():映射转换sorted():排序distinct():去重limit():限制数量产生结果或副作用,触发流水线执行
forEach():遍历collect():收集结果reduce():归约count():计数findFirst():查找第一个传统方式:
// 传统方式:过滤年龄大于18的人并收集名字
List<Person> people = getPeople();
List<String> adultNames = new ArrayList<>();
for (Person person : people) {
if (person.getAge() > 18) {
adultNames.add(person.getName());
}
}
Stream方式:
// Stream方式:更简洁、声明式
List<String> adultNames = people.stream()
.filter(person -> person.getAge() > 18)
.map(Person::getName)
.collect(Collectors.toList());
传统方式:
// 计算整数列表中大于10的数的平均值
List<Integer> numbers = Arrays.asList(5, 12, 8, 20, 15, 3);
int sum = 0;
int count = 0;
for (Integer num : numbers) {
if (num > 10) {
sum += num;
count++;
}
}
double average = count > 0 ? (double) sum / count : 0;
Stream方式:
OptionalDouble average = numbers.stream()
.filter(num -> num > 10)
.mapToInt(Integer::intValue)
.average();
// 将人员按城市分组,并计算每个城市的平均年龄
Map<String, Double> cityAverageAge = people.stream()
.collect(Collectors.groupingBy(
Person::getCity,
Collectors.averagingInt(Person::getAge)
));
// 查找年龄最大的3个人
List<Person> oldestThree = people.stream()
.sorted((p1, p2) -> p2.getAge() - p1.getAge())
.limit(3)
.collect(Collectors.toList());
// 性能测试代码
public class PerformanceTest {
public static void main(String[] args) {
List<Integer> numbers = IntStream.rangeClosed(1, 10_000_000)
.boxed()
.collect(Collectors.toList());
// 传统方式
long startTime = System.currentTimeMillis();
List<Integer> traditionalResult = new ArrayList<>();
for (Integer num : numbers) {
if (num % 2 == 0) {
traditionalResult.add(num * 2);
}
}
long traditionalTime = System.currentTimeMillis() - startTime;
// Stream方式(顺序流)
startTime = System.currentTimeMillis();
List<Integer> streamResult = numbers.stream()
.filter(num -> num % 2 == 0)
.map(num -> num * 2)
.collect(Collectors.toList());
long streamTime = System.currentTimeMillis() - startTime;
// 并行流方式
startTime = System.currentTimeMillis();
List<Integer> parallelResult = numbers.parallelStream()
.filter(num -> num % 2 == 0)
.map(num -> num * 2)
.collect(Collectors.toList());
long parallelTime = System.currentTimeMillis() - startTime;
System.out.println("传统方式: " + traditionalTime + "ms");
System.out.println("Stream方式: " + streamTime + "ms");
System.out.println("并行Stream: " + parallelTime + "ms");
}
}
测试结果:
传统方式: 245ms
Stream方式: 312ms
并行Stream: 78ms
// 分组和聚合性能对比
public class GroupByPerformance {
public static void main(String[] args) {
List<Person> people = generatePeople(1_000_000);
// 传统分组
long start = System.currentTimeMillis();
Map<String, List<Person>> traditionalGroups = new HashMap<>();
for (Person p : people) {
traditionalGroups
.computeIfAbsent(p.getCity(), k -> new ArrayList<>())
.add(p);
}
System.out.println("传统分组: " + (System.currentTimeMillis() - start) + "ms");
// Stream分组
start = System.currentTimeMillis();
Map<String, List<Person>> streamGroups = people.stream()
.collect(Collectors.groupingBy(Person::getCity));
System.out.println("Stream分组: " + (System.currentTimeMillis() - start) + "ms");
// 并行Stream分组
start = System.currentTimeMillis();
Map<String, List<Person>> parallelGroups = people.parallelStream()
.collect(Collectors.groupingBy(Person::getCity));
System.out.println("并行Stream分组: " + (System.currentTimeMillis() - start) + "ms");
}
}
parallelStream()可显著提升性能// 1. 使用基本类型流避免装箱开销
IntStream.range(0, 1000) // 比Stream<Integer>更高效
.sum();
// 2. 并行流使用注意事项
List<Integer> result = largeList.parallelStream()
.filter(this::expensiveFilter) // 确保过滤操作足够"重"
.map(this::expensiveMap) // 确保映射操作足够"重"
.collect(Collectors.toList());
// 3. 避免在流中执行耗时操作
// 错误示例 - 每次循环都创建新对象
items.stream()
.map(item -> {
ExpensiveObject obj = new ExpensiveObject(); // 避免!
return obj.process(item);
});
// 4. 使用短路操作优化
Optional<Person> result = people.stream()
.filter(p -> p.getAge() > 18)
.filter(p -> p.getCity().equals("Beijing"))
.findFirst(); // 找到第一个就停止
// 创建高效的字符串连接收集器
String concatenated = items.stream()
.collect(Collector.of(
StringBuilder::new, // 供应器
StringBuilder::append, // 累加器
StringBuilder::append, // 组合器(用于并行)
StringBuilder::toString // 完成器
));
// 生成斐波那契数列
Stream.iterate(new int[]{0, 1}, t -> new int[]{t[1], t[0] + t[1]})
.limit(10)
.map(t -> t[0])
.forEach(System.out::println);
// 分区:按条件分为两部分
Map<Boolean, List<Person>> partitioned = people.stream()
.collect(Collectors.partitioningBy(p -> p.getAge() >= 18));
// 多级分组
Map<String, Map<String, List<Person>>> multiGroup = people.stream()
.collect(Collectors.groupingBy(
Person::getCountry,
Collectors.groupingBy(Person::getCity)
));
保持流操作纯函数化:避免在lambda中修改外部状态
优先使用方法引用:提高代码可读性
// 使用方法引用
.map(Person::getName)
// 而不是
.map(person -> person.getName())
注意流的重用:流是一次性的,不能重复使用
合理使用并行流:
调试技巧:
// 使用peek进行调试
list.stream()
.peek(e -> System.out.println("过滤前: " + e))
.filter(e -> e > 10)
.peek(e -> System.out.println("过滤后: " + e))
.collect(Collectors.toList());
Java Stream API代表了集合处理方式的革命性进步。它不仅使代码更加简洁、可读,而且通过声明式编程让开发者的意图更加清晰。虽然在某些简单场景下传统循环可能有微弱的性能优势,但Stream在可维护性、可读性和并行处理能力方面具有明显优势。
选择建议:
Stream不仅是语法糖,更是思维方式的转变——从"如何做"到"做什么"的转变。掌握Stream,你将写出更符合现代Java编程风格的优雅代码。
注:本文所有性能测试结果基于特定环境,实际结果可能因JVM版本、硬件配置和数据特性而有所不同,建议在实际项目中根据具体场景进行性能测试。