引言:为什么需要Stream API?
在Java 8之前,处理集合数据通常需要繁琐的循环和临时变量。Stream API的引入彻底改变了这一现状,它提供了一种声明式的、高效的数据处理方式。想象一下,你有一个装满各种水果的篮子,Stream API就像是一条智能流水线,可以轻松地对水果进行筛选、转换、排序等操作。
一、Stream基础:创建与简单操作
1.1 创建Stream的多种方式
import java.util.*;
import java.util.stream.*;
public class StreamCreation {
public static void main(String[] args) {
System.out.println("=== 创建Stream的5种方式 ===");
// 方式1:从集合创建(最常用)
List<String> fruits = Arrays.asList("Apple", "Banana", "Orange", "Mango");
Stream<String> stream1 = fruits.stream();
System.out.println("从List创建: " + stream1.count() + " 个元素");
// 方式2:从数组创建
String[] colors = {"Red", "Green", "Blue"};
Stream<String> stream2 = Arrays.stream(colors);
System.out.println("从数组创建: " + stream2.count() + " 个颜色");
// 方式3:使用Stream.of()直接创建
Stream<String> stream3 = Stream.of("Java", "Python", "JavaScript");
stream3.forEach(lang -> System.out.println("语言: " + lang));
// 方式4:生成无限流(需要限制大小)
System.out.print("5个随机数: ");
Stream<Double> randomStream = Stream.generate(Math::random).limit(5);
randomStream.forEach(num -> System.out.printf("%.2f ", num));
System.out.println();
// 方式5:使用迭代创建规律序列
System.out.print("前5个偶数: ");
Stream<Integer> evenNumbers = Stream.iterate(0, n -> n + 2).limit(5);
evenNumbers.forEach(n -> System.out.print(n + " "));
System.out.println();
// 方式6:从Map创建
Map<String, Integer> scores = new HashMap<>();
scores.put("Alice", 85);
scores.put("Bob", 92);
scores.put("Charlie", 78);
System.out.println("\n=== 处理Map数据 ===");
// 获取键的Stream
scores.keySet().stream()
.forEach(name -> System.out.println("学生: " + name));
// 获取值的Stream
int total = scores.values().stream()
.mapToInt(Integer::intValue)
.sum();
System.out.println("总分: " + total);
}
}
1.2 最常用的Stream操作:过滤与映射
public class FilterMapDemo {
public static void main(String[] args) {
List<Student> students = Arrays.asList(
new Student("Alice", 22, 85.5, "计算机科学"),
new Student("Bob", 21, 92.0, "数学"),
new Student("Charlie", 23, 78.0, "物理"),
new Student("David", 20, 88.5, "计算机科学"),
new Student("Eve", 22, 95.0, "数学")
);
System.out.println("=== 1. 基础过滤操作 ===");
// 找出计算机科学专业的学生
List<Student> csStudents = students.stream()
.filter(s -> s.getMajor().equals("计算机科学"))
.collect(Collectors.toList());
System.out.println("计算机科学专业的学生:");
csStudents.forEach(s -> System.out.println(" - " + s.getName()));
// 找出成绩大于90分的学生
System.out.println("\n成绩大于90分的学生:");
students.stream()
.filter(s -> s.getScore() > 90)
.forEach(s -> System.out.println(" - " + s.getName() + ": " + s.getScore()));
System.out.println("\n=== 2. 映射操作 ===");
// 提取所有学生名字(从对象映射到字符串)
List<String> names = students.stream()
.map(Student::getName) // 方法引用,等价于 s -> s.getName()
.collect(Collectors.toList());
System.out.println("所有学生名字: " + names);
// 获取每个学生的成绩等级
List<String> grades = students.stream()
.map(s -> {
double score = s.getScore();
if (score >= 90) return "优秀";
else if (score >= 80) return "良好";
else if (score >= 70) return "中等";
else return "及格";
})
.collect(Collectors.toList());
System.out.println("成绩等级: " + grades);
System.out.println("\n=== 3. 组合使用:过滤 + 映射 ===");
// 找出数学专业学生的名字
List<String> mathStudentNames = students.stream()
.filter(s -> s.getMajor().equals("数学"))
.map(Student::getName)
.collect(Collectors.toList());
System.out.println("数学专业学生: " + mathStudentNames);
// 计算计算机科学专业的平均分
double csAverage = students.stream()
.filter(s -> s.getMajor().equals("计算机科学"))
.mapToDouble(Student::getScore) // 转换为DoubleStream以进行数值计算
.average()
.orElse(0.0); // 如果没有元素,返回0.0
System.out.printf("计算机科学专业平均分: %.2f\n", csAverage);
}
}
// 学生类定义
class Student {
private String name;
private int age;
private double score;
private String major;
public Student(String name, int age, double score, String major) {
this.name = name;
this.age = age;
this.score = score;
this.major = major;
}
// Getter方法
public String getName() { return name; }
public int getAge() { return age; }
public double getScore() { return score; }
public String getMajor() { return major; }
@Override
public String toString() {
return name + "(" + age + "岁, " + score + "分, " + major + ")";
}
}
二、Stream进阶:排序、去重与聚合
2.1 数据排序与去重
public class SortDistinctDemo {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(7, 3, 5, 7, 2, 3, 8, 1, 5, 9);
System.out.println("原始数据: " + numbers);
System.out.println("\n=== 1. 基本排序 ===");
// 自然顺序排序(升序)
List<Integer> sortedAsc = numbers.stream()
.sorted()
.collect(Collectors.toList());
System.out.println("升序排序: " + sortedAsc);
// 降序排序
List<Integer> sortedDesc = numbers.stream()
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList());
System.out.println("降序排序: " + sortedDesc);
System.out.println("\n=== 2. 去重操作 ===");
List<Integer> distinctNumbers = numbers.stream()
.distinct()
.sorted()
.collect(Collectors.toList());
System.out.println("去重后排序: " + distinctNumbers);
System.out.println("\n=== 3. 对象排序 ===");
List<Product> products = Arrays.asList(
new Product("笔记本电脑", 5999.99, 50),
new Product("手机", 2999.99, 100),
new Product("平板电脑", 3999.99, 30),
new Product("耳机", 499.99, 200)
);
// 按价格升序排序
System.out.println("按价格升序排序:");
products.stream()
.sorted(Comparator.comparing(Product::getPrice))
.forEach(p -> System.out.println(" - " + p));
// 按库存降序排序
System.out.println("\n按库存降序排序:");
products.stream()
.sorted(Comparator.comparing(Product::getStock).reversed())
.forEach(p -> System.out.println(" - " + p));
// 多重排序:先按价格,再按库存
System.out.println("\n多重排序(价格→库存):");
products.stream()
.sorted(Comparator.comparing(Product::getPrice)
.thenComparing(Product::getStock))
.forEach(p -> System.out.println(" - " + p));
System.out.println("\n=== 4. 复杂去重场景 ===");
List<Person> people = Arrays.asList(
new Person("Alice", "北京"),
new Person("Bob", "上海"),
new Person("Alice", "北京"), // 重复
new Person("Charlie", "广州"),
new Person("Bob", "深圳") // 名字相同但城市不同,不算重复
);
// 根据姓名去重
List<Person> distinctByName = people.stream()
.distinct() // 这行不会起作用,因为Person没有重写equals方法
.collect(Collectors.toList());
System.out.println("简单distinct无效: " + distinctByName.size() + " 人");
// 正确做法:使用自定义去重逻辑
List<Person> trulyDistinct = people.stream()
.collect(Collectors.collectingAndThen(
Collectors.toMap(
Person::getName, // 以姓名为键
p -> p, // 值保持为Person对象
(p1, p2) -> p1 // 如果键冲突,保留第一个
),
map -> new ArrayList<>(map.values())
));
System.out.println("根据姓名去重后: " + trulyDistinct.size() + " 人");
trulyDistinct.forEach(p -> System.out.println(" - " + p));
}
}
// 商品类
class Product {
private String name;
private double price;
private int stock;
public Product(String name, double price, int stock) {
this.name = name;
this.price = price;
this.stock = stock;
}
public String getName() { return name; }
public double getPrice() { return price; }
public int getStock() { return stock; }
@Override
public String toString() {
return String.format("%s (¥%.2f, 库存: %d)", name, price, stock);
}
}
// 人类
class Person {
private String name;
private String city;
public Person(String name, String city) {
this.name = name;
this.city = city;
}
public String getName() { return name; }
public String getCity() { return city; }
@Override
public String toString() {
return name + " - " + city;
}
}
2.2 聚合操作:统计与分组
public class AggregationDemo {
public static void main(String[] args) {
List<Employee> employees = Arrays.asList(
new Employee("Alice", "开发部", 8500),
new Employee("Bob", "开发部", 9200),
new Employee("Charlie", "市场部", 7800),
new Employee("David", "开发部", 8800),
new Employee("Eve", "市场部", 9500),
new Employee("Frank", "人事部", 6500)
);
System.out.println("=== 1. 基本统计 ===");
// 计算总工资
double totalSalary = employees.stream()
.mapToDouble(Employee::getSalary)
.sum();
System.out.printf("总工资: ¥%,.2f\n", totalSalary);
// 计算平均工资
double averageSalary = employees.stream()
.mapToDouble(Employee::getSalary)
.average()
.orElse(0.0);
System.out.printf("平均工资: ¥%,.2f\n", averageSalary);
// 找出最高和最低工资
DoubleSummaryStatistics stats = employees.stream()
.mapToDouble(Employee::getSalary)
.summaryStatistics();
System.out.println("工资统计:");
System.out.printf(" 最高: ¥%,.2f\n", stats.getMax());
System.out.printf(" 最低: ¥%,.2f\n", stats.getMin());
System.out.printf(" 平均: ¥%,.2f\n", stats.getAverage());
System.out.printf(" 总和: ¥%,.2f\n", stats.getSum());
System.out.println(" 人数: " + stats.getCount());
System.out.println("\n=== 2. 分组操作 ===");
// 按部门分组
Map<String, List<Employee>> byDepartment = employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment));
System.out.println("按部门分组:");
byDepartment.forEach((dept, empList) -> {
System.out.println(" " + dept + ": " +
empList.stream()
.map(Employee::getName)
.collect(Collectors.joining(", ")));
});
// 按部门统计平均工资
Map<String, Double> avgSalaryByDept = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.averagingDouble(Employee::getSalary)
));
System.out.println("\n各部门平均工资:");
avgSalaryByDept.forEach((dept, avg) ->
System.out.printf(" %s: ¥%,.2f\n", dept, avg));
// 多层分组:先按部门,再按工资范围
Map<String, Map<String, List<Employee>>> multiLevelGroup = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.groupingBy(emp -> {
double salary = emp.getSalary();
if (salary < 8000) return "普通";
else if (salary < 9000) return "中等";
else return "高级";
})
));
System.out.println("\n多层分组(部门→薪资级别):");
multiLevelGroup.forEach((dept, salaryGroups) -> {
System.out.println(" " + dept + ":");
salaryGroups.forEach((level, empList) -> {
System.out.println(" " + level + ": " +
empList.stream()
.map(Employee::getName)
.collect(Collectors.joining(", ")));
});
});
System.out.println("\n=== 3. 分区操作(特殊的分组,只有true/false两组)===");
// 将员工分为高工资(>=8000)和低工资两组
Map<Boolean, List<Employee>> partitioned = employees.stream()
.collect(Collectors.partitioningBy(
emp -> emp.getSalary() >= 8000
));
System.out.println("按工资是否>=8000分区:");
System.out.println(" 高工资组:");
partitioned.get(true).forEach(emp ->
System.out.println(" - " + emp.getName() + ": ¥" + emp.getSalary()));
System.out.println(" 低工资组:");
partitioned.get(false).forEach(emp ->
System.out.println(" - " + emp.getName() + ": ¥" + emp.getSalary()));
System.out.println("\n=== 4. 连接字符串 ===");
// 将所有员工姓名连接成一个字符串
String allNames = employees.stream()
.map(Employee::getName)
.collect(Collectors.joining(", ", "员工列表: [", "]"));
System.out.println(allNames);
// 按部门连接员工姓名
Map<String, String> namesByDept = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.mapping(
Employee::getName,
Collectors.joining("、", "(", ")")
)
));
System.out.println("\n各部门员工:");
namesByDept.forEach((dept, names) ->
System.out.println(" " + dept + ": " + names));
}
}
class Employee {
private String name;
private String department;
private double salary;
public Employee(String name, String department, double salary) {
this.name = name;
this.department = department;
this.salary = salary;
}
public String getName() { return name; }
public String getDepartment() { return department; }
public double getSalary() { return salary; }
}
三、Stream实战:真实场景应用
3.1 数据处理管道
public class DataProcessingPipeline {
public static void main(String[] args) {
// 模拟从数据库获取的订单数据
List<Order> orders = Arrays.asList(
new Order(1, "Alice", "2024-01-15", 1500.0, "已完成"),
new Order(2, "Bob", "2024-01-16", 800.0, "已完成"),
new Order(3, "Charlie", "2024-01-16", 2500.0, "已取消"),
new Order(4, "Alice", "2024-01-17", 1200.0, "已完成"),
new Order(5, "David", "2024-01-17", 3000.0, "已完成"),
new Order(6, "Eve", "2024-01-18", 900.0, "处理中"),
new Order(7, "Bob", "2024-01-18", 1800.0, "已完成"),
new Order(8, "Frank", "2024-01-19", 700.0, "已完成")
);
System.out.println("=== 电商订单数据分析 ===");
// 场景1:计算已完成订单的总金额
double totalCompleted = orders.stream()
.filter(order -> "已完成".equals(order.getStatus()))
.mapToDouble(Order::getAmount)
.sum();
System.out.printf("已完成订单总金额: ¥%,.2f\n", totalCompleted);
// 场景2:找出消费金额最高的前3名客户
System.out.println("\n消费金额TOP 3客户:");
Map<String, Double> customerSpending = orders.stream()
.filter(order -> "已完成".equals(order.getStatus()))
.collect(Collectors.groupingBy(
Order::getCustomerName,
Collectors.summingDouble(Order::getAmount)
));
customerSpending.entrySet().stream()
.sorted(Map.Entry.<String, Double>comparingByValue().reversed())
.limit(3)
.forEach(entry ->
System.out.printf(" %s: ¥%,.2f\n", entry.getKey(), entry.getValue()));
// 场景3:按日期统计每日订单量
System.out.println("\n每日订单统计:");
Map<String, Long> ordersByDate = orders.stream()
.collect(Collectors.groupingBy(
Order::getOrderDate,
Collectors.counting()
));
ordersByDate.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.forEach(entry ->
System.out.printf(" %s: %d 笔订单\n", entry.getKey(), entry.getValue()));
// 场景4:复杂的业务逻辑 - 找出需要关注的客户
System.out.println("\n=== 需要关注的客户 ===");
System.out.println("1. 有取消订单记录的客户:");
Set<String> customersWithCancellations = orders.stream()
.filter(order -> "已取消".equals(order.getStatus()))
.map(Order::getCustomerName)
.collect(Collectors.toSet());
customersWithCancellations.forEach(customer ->
System.out.println(" - " + customer));
System.out.println("\n2. 单笔订单金额超过2000的客户:");
orders.stream()
.filter(order -> order.getAmount() > 2000)
.map(Order::getCustomerName)
.distinct()
.forEach(customer -> System.out.println(" - " + customer));
System.out.println("\n3. 近3天内有订单且平均金额>1000的活跃客户:");
// 假设今天是2024-01-19,我们看前3天的数据
Set<String> recentDates = Set.of("2024-01-17", "2024-01-18", "2024-01-19");
Map<String, DoubleSummaryStatistics> recentStats = orders.stream()
.filter(order -> recentDates.contains(order.getOrderDate()))
.filter(order -> "已完成".equals(order.getStatus()))
.collect(Collectors.groupingBy(
Order::getCustomerName,
Collectors.summarizingDouble(Order::getAmount)
));
recentStats.entrySet().stream()
.filter(entry -> entry.getValue().getAverage() > 1000)
.forEach(entry -> {
String customer = entry.getKey();
DoubleSummaryStatistics stat = entry.getValue();
System.out.printf(" - %s: 订单数=%d, 平均金额=¥%,.2f\n",
customer, stat.getCount(), stat.getAverage());
});
// 场景5:生成报表数据
System.out.println("\n=== 月度销售报表 ===");
Map<String, Map<String, Double>> report = orders.stream()
.filter(order -> "已完成".equals(order.getStatus()))
.collect(Collectors.groupingBy(
Order::getOrderDate,
Collectors.groupingBy(
Order::getCustomerName,
Collectors.summingDouble(Order::getAmount)
)
));
report.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.forEach(dateEntry -> {
System.out.println("\n日期: " + dateEntry.getKey());
dateEntry.getValue().forEach((customer, amount) ->
System.out.printf(" 客户 %s: ¥%,.2f\n", customer, amount));
});
}
}
class Order {
private int id;
private String customerName;
private String orderDate;
private double amount;
private String status;
public Order(int id, String customerName, String orderDate,
double amount, String status) {
this.id = id;
this.customerName = customerName;
this.orderDate = orderDate;
this.amount = amount;
this.status = status;
}
// Getter方法
public int getId() { return id; }
public String getCustomerName() { return customerName; }
public String getOrderDate() { return orderDate; }
public double getAmount() { return amount; }
public String getStatus() { return status; }
}
3.2 并行流处理大数据
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.atomic.AtomicInteger;
public class ParallelStreamDemo {
public static void main(String[] args) {
System.out.println("=== 并行流性能演示 ===");
// 创建大量数据
List<Integer> largeList = new ArrayList<>();
for (int i = 0; i < 10_000_000; i++) {
largeList.add((int) (Math.random() * 1000));
}
System.out.println("数据量: " + largeList.size() + " 个数字");
// 测试1:顺序流
long startTime = System.currentTimeMillis();
long countSequential = largeList.stream()
.filter(n -> n % 2 == 0) // 筛选偶数
.count();
long sequentialTime = System.currentTimeMillis() - startTime;
System.out.printf("顺序流 - 偶数个数: %,d, 耗时: %,d 毫秒\n",
countSequential, sequentialTime);
// 测试2:并行流
startTime = System.currentTimeMillis();
long countParallel = largeList.parallelStream()
.filter(n -> n % 2 == 0)
.count();
long parallelTime = System.currentTimeMillis() - startTime;
System.out.printf("并行流 - 偶数个数: %,d, 耗时: %,d 毫秒\n",
countParallel, parallelTime);
System.out.printf("性能提升: %.1f%%\n",
(1 - (double)parallelTime / sequentialTime) * 100);
System.out.println("\n=== 并行流注意事项 ===");
// 注意1:有状态操作的线程安全问题
System.out.println("1. 有状态操作的线程安全问题:");
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 错误示例:在并行流中使用非线程安全的累加
List<Integer> unsafeResult = new ArrayList<>();
numbers.parallelStream()
.map(n -> n * 2)
.forEach(unsafeResult::add); // 可能丢失数据或出错
System.out.println(" 非线程安全集合大小: " + unsafeResult.size());
// 正确示例:使用线程安全收集
List<Integer> safeResult = numbers.parallelStream()
.map(n -> n * 2)
.collect(Collectors.toList());
System.out.println(" 线程安全集合大小: " + safeResult.size());
// 注意2:顺序依赖的操作不适合并行
System.out.println("\n2. 顺序依赖的操作:");
System.out.println(" 顺序流(正确):");
numbers.stream()
.limit(5)
.forEach(n -> System.out.print(n + " "));
System.out.println("\n 并行流(顺序不确定):");
numbers.parallelStream()
.limit(5)
.forEach(n -> System.out.print(n + " "));
// 注意3:自定义线程池
System.out.println("\n\n3. 自定义并行流线程池:");
ForkJoinPool customThreadPool = new ForkJoinPool(4);
AtomicInteger threadCounter = new AtomicInteger(0);
Set<String> threadNames = Collections.synchronizedSet(new HashSet<>());
try {
customThreadPool.submit(() -> {
numbers.parallelStream()
.forEach(n -> {
String threadName = Thread.currentThread().getName();
threadNames.add(threadName);
threadCounter.incrementAndGet();
});
}).get();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(" 使用的线程数: " + threadNames.size());
System.out.println(" 总处理次数: " + threadCounter.get());
// 注意4:I/O密集型任务不一定适合并行流
System.out.println("\n4. 适合与不适合并行流的场景:");
System.out.println(" ✅ 适合: CPU密集型计算、大数据集合处理");
System.out.println(" ⚠️ 谨慎: I/O密集型操作(可能阻塞线程)");
System.out.println(" ❌ 不适合: 顺序依赖、小数据量、非线程安全操作");
// 实际案例:并行处理图片数据
System.out.println("\n=== 实际案例:图片批量处理 ===");
List<Image> images = Arrays.asList(
new Image("photo1.jpg", 1920, 1080, 2500),
new Image("photo2.jpg", 1280, 720, 1500),
new Image("photo3.jpg", 3840, 2160, 8000),
new Image("photo4.jpg", 800, 600, 500),
new Image("photo5.jpg", 2560, 1440, 3500)
);
// 并行压缩图片(模拟)
List<Image> compressedImages = images.parallelStream()
.map(image -> {
// 模拟耗时处理
try { Thread.sleep(100); } catch (InterruptedException e) {}
// 压缩图片:尺寸减半,大小减少60%
return new Image(
"compressed_" + image.getFilename(),
image.getWidth() / 2,
image.getHeight() / 2,
(int) (image.getSize() * 0.4)
);
})
.collect(Collectors.toList());
System.out.println("图片压缩完成:");
compressedImages.forEach(image ->
System.out.printf(" %s: %dx%d, %.1fKB\n",
image.getFilename(),
image.getWidth(),
image.getHeight(),
image.getSize() / 1024.0));
}
}
class Image {
private String filename;
private int width;
private int height;
private int size; // 字节
public Image(String filename, int width, int height, int size) {
this.filename = filename;
this.width = width;
this.height = height;
this.size = size;
}
public String getFilename() { return filename; }
public int getWidth() { return width; }
public int getHeight() { return height; }
public int getSize() { return size; }
}
四、Stream高级技巧与最佳实践
4.1 性能优化与调试技巧
public class StreamOptimization {
public static void main(String[] args) {
System.out.println("=== Stream性能优化技巧 ===");
List<Product> products = createTestProducts(100000);
// 技巧1:使用基本类型流避免装箱开销
System.out.println("\n1. 基本类型流优化:");
long startTime = System.currentTimeMillis();
// 对象流(有装箱开销)
double avgPrice1 = products.stream()
.mapToDouble(Product::getPrice)
.average()
.orElse(0.0);
long time1 = System.currentTimeMillis() - startTime;
startTime = System.currentTimeMillis();
// 基本类型流(优化后)
DoubleSummaryStatistics stats = products.stream()
.mapToDouble(Product::getPrice)
.summaryStatistics();
long time2 = System.currentTimeMillis() - startTime;
System.out.printf(" 对象流耗时: %d ms\n", time1);
System.out.printf(" 基本类型流耗时: %d ms\n", time2);
System.out.printf(" 性能提升: %.1f%%\n",
(1 - (double)time2 / time1) * 100);
// 技巧2:适当使用limit和短路操作
System.out.println("\n2. 使用limit优化:");
startTime = System.currentTimeMillis();
List<Product> expensiveProducts = products.stream()
.filter(p -> {
// 模拟复杂计算
try { Thread.sleep(1); } catch (InterruptedException e) {}
return p.getPrice() > 500;
})
.limit(10) // 只要前10个,后面的不会计算
.collect(Collectors.toList());
long timeWithLimit = System.currentTimeMillis() - startTime;
System.out.printf(" 使用limit找到10个高价商品: %d ms\n", timeWithLimit);
// 技巧3:合并多个操作
System.out.println("\n3. 操作合并优化:");
// 不好的做法:多次遍历
startTime = System.currentTimeMillis();
long expensiveCount = products.stream()
.filter(p -> p.getPrice() > 500)
.count();
double expensiveAvg = products.stream()
.filter(p -> p.getPrice() > 500)
.mapToDouble(Product::getPrice)
.average()
.orElse(0.0);
long timeMultiplePasses = System.currentTimeMillis() - startTime;
// 好的做法:一次遍历,多个统计
startTime = System.currentTimeMillis();
DoubleSummaryStatistics expensiveStats = products.stream()
.filter(p -> p.getPrice() > 500)
.mapToDouble(Product::getPrice)
.summaryStatistics();
long timeSinglePass = System.currentTimeMillis() - startTime;
System.out.printf(" 多次遍历耗时: %d ms\n", timeMultiplePasses);
System.out.printf(" 单次遍历耗时: %d ms\n", timeSinglePass);
// 技巧4:避免在流中创建过多对象
System.out.println("\n4. 避免不必要的对象创建:");
// 不好的做法:在map中创建字符串
startTime = System.currentTimeMillis();
List<String> badDescriptions = products.stream()
.map(p -> p.getName() + " - ¥" + p.getPrice()) // 每次创建新字符串
.limit(1000)
.collect(Collectors.toList());
long timeBad = System.currentTimeMillis() - startTime;
// 好的做法:延迟字符串创建
startTime = System.currentTimeMillis();
List<String> goodDescriptions = products.stream()
.limit(1000)
.map(p -> String.format("%s - ¥%.2f", p.getName(), p.getPrice()))
.collect(Collectors.toList());
long timeGood = System.currentTimeMillis() - startTime;
System.out.printf(" 每次创建字符串: %d ms\n", timeBad);
System.out.printf(" 使用String.format: %d ms\n", timeGood);
// 技巧5:调试Stream流水线
System.out.println("\n5. Stream调试技巧:");
System.out.println(" 使用peek()调试:");
List<Product> result = products.stream()
.peek(p -> System.out.println("原始: " + p))
.filter(p -> p.getPrice() > 100)
.peek(p -> System.out.println("过滤后: " + p))
.sorted(Comparator.comparing(Product::getPrice))
.peek(p -> System.out.println("排序后: " + p))
.limit(3)
.peek(p -> System.out.println("限制后: " + p))
.collect(Collectors.toList());
System.out.println(" 最终结果: " + result.size() + " 个商品");
// 技巧6:及早过滤
System.out.println("\n6. 操作顺序优化(及早过滤):");
// 不好的顺序:先映射再过滤
startTime = System.currentTimeMillis();
List<String> badOrder = products.stream()
.map(p -> expensiveOperation(p)) // 先执行昂贵操作
.filter(name -> name.length() > 5)
.limit(100)
.collect(Collectors.toList());
long timeBadOrder = System.currentTimeMillis() - startTime;
// 好的顺序:先过滤再映射
startTime = System.currentTimeMillis();
List<String> goodOrder = products.stream()
.filter(p -> p.getName().length() > 5) // 先过滤
.limit(100) // 及早限制
.map(p -> expensiveOperation(p)) // 只对需要的元素执行昂贵操作
.collect(Collectors.toList());
long timeGoodOrder = System.currentTimeMillis() - startTime;
System.out.printf(" 先映射后过滤: %d ms\n", timeBadOrder);
System.out.printf(" 先过滤后映射: %d ms\n", timeGoodOrder);
System.out.printf(" 优化效果: %.1f%%\n",
(1 - (double)timeGoodOrder / timeBadOrder) * 100);
}
private static List<Product> createTestProducts(int count) {
List<Product> products = new ArrayList<>();
String[] categories = {"电子产品", "图书", "服装", "食品", "家居"};
String[] adjectives = {"高级", "智能", "经典", "时尚", "实用"};
String[] nouns = {"手机", "电脑", "书籍", "衬衫", "零食"};
Random random = new Random();
for (int i = 0; i < count; i++) {
String name = adjectives[random.nextInt(adjectives.length)] +
nouns[random.nextInt(nouns.length)];
String category = categories[random.nextInt(categories.length)];
double price = 10 + random.nextDouble() * 1000;
int stock = random.nextInt(1000);
products.add(new Product(name, category, price, stock));
}
return products;
}
private static String expensiveOperation(Product p) {
// 模拟耗时操作
try { Thread.sleep(1); } catch (InterruptedException e) {}
return p.getName().toUpperCase();
}
}
class Product {
private String name;
private String category;
private double price;
private int stock;
public Product(String name, String category, double price, int stock) {
this.name = name;
this.category = category;
this.price = price;
this.stock = stock;
}
public String getName() { return name; }
public String getCategory() { return category; }
public double getPrice() { return price; }
public int getStock() { return stock; }
@Override
public String toString() {
return String.format("%s (%.2f元)", name, price);
}
}
4.2 Stream与Optional的结合使用
import java.util.Optional;
public class StreamWithOptional {
public static void main(String[] args) {
System.out.println("=== Stream与Optional结合使用 ===");
List<User> users = Arrays.asList(
new User("Alice", 25, "alice@example.com"),
new User("Bob", 30, null), // 没有邮箱
new User("Charlie", 22, "charlie@example.com"),
new User("David", 35, ""), // 空邮箱
new User("Eve", 28, "eve@example.com")
);
// 场景1:安全地处理可能为null的值
System.out.println("\n1. 安全获取用户邮箱(过滤null和空值):");
List<String> validEmails = users.stream()
.map(User::getEmail) // 获取Optional<String>
.filter(Optional::isPresent) // 过滤掉空的Optional
.map(Optional::get) // 获取实际值
.filter(email -> !email.isEmpty()) // 过滤空字符串
.collect(Collectors.toList());
System.out.println(" 有效邮箱: " + validEmails);
// 使用flatMap的更优雅写法
List<String> emails = users.stream()
.map(User::getEmail)
.flatMap(opt -> opt.map(Stream::of).orElseGet(Stream::empty))
.filter(email -> !email.isEmpty())
.collect(Collectors.toList());
System.out.println(" (使用flatMap)有效邮箱: " + emails);
// 场景2:查找第一个有邮箱的用户
System.out.println("\n2. 查找第一个有邮箱的用户:");
Optional<User> firstWithEmail = users.stream()
.filter(user -> user.getEmail()
.map(email -> !email.isEmpty())
.orElse(false))
.findFirst();
firstWithEmail.ifPresentOrElse(
user -> System.out.println(" 找到: " + user.getName()),
() -> System.out.println(" 没有找到有邮箱的用户")
);
// 场景3:安全地转换和处理
System.out.println("\n3. 安全转换(不会因null而崩溃):");
List<String> emailDomains = users.stream()
.map(User::getEmail)
.filter(Optional::isPresent)
.map(Optional::get)
.filter(email -> !email.isEmpty())
.map(email -> {
// 安全地提取域名
int atIndex = email.indexOf('@');
return atIndex > 0 ? email.substring(atIndex + 1) : "无效邮箱";
})
.collect(Collectors.toList());
System.out.println(" 邮箱域名: " + emailDomains);
// 场景4:使用Optional的链式调用
System.out.println("\n4. Optional链式调用:");
users.stream()
.forEach(user -> {
String emailInfo = user.getEmail()
.filter(email -> !email.isEmpty())
.map(email -> "邮箱: " + email)
.orElse("无有效邮箱");
System.out.println(" " + user.getName() + " - " + emailInfo);
});
// 场景5:复杂的条件查找
System.out.println("\n5. 复杂条件查找:");
// 找到年龄大于25且邮箱包含"example"的第一个用户
Optional<String> result = users.stream()
.filter(user -> user.getAge() > 25)
.map(user -> user.getEmail()
.filter(email -> email.contains("example"))
.map(email -> user.getName() + "的邮箱: " + email)
)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
System.out.println(" 查找结果: " + result.orElse("未找到"));
// 场景6:使用orElse提供默认值
System.out.println("\n6. 提供默认值:");
List<String> allEmailsWithDefault = users.stream()
.map(user -> user.getEmail()
.filter(email -> !email.isEmpty())
.orElse("无邮箱"))
.collect(Collectors.toList());
System.out.println(" 所有用户的邮箱(含默认值):");
allEmailsWithDefault.forEach(email -> System.out.println(" - " + email));
// 场景7:实际应用案例 - 用户注册验证
System.out.println("\n7. 实际案例:用户注册验证");
List<RegistrationRequest> requests = Arrays.asList(
new RegistrationRequest("alice123", "Alice", "alice@example.com"),
new RegistrationRequest(null, "Bob", "bob@example.com"), // 用户名null
new RegistrationRequest("charlie", "Charlie", "invalid-email"), // 无效邮箱
new RegistrationRequest("david", "", "david@example.com"), // 空姓名
new RegistrationRequest("eve123", "Eve", "eve@example.com")
);
List<RegistrationRequest> validRequests = requests.stream()
.filter(req -> Optional.ofNullable(req.getUsername())
.filter(username -> !username.isEmpty())
.isPresent())
.filter(req -> Optional.ofNullable(req.getFullName())
.filter(name -> !name.isEmpty())
.isPresent())
.filter(req -> Optional.ofNullable(req.getEmail())
.filter(email -> email.contains("@") && email.contains("."))
.isPresent())
.collect(Collectors.toList());
System.out.println(" 有效注册请求: " + validRequests.size() + " 个");
validRequests.forEach(req ->
System.out.println(" - " + req.getUsername()));
}
}
class User {
private String name;
private int age;
private String email; // 可能为null
public User(String name, int age, String email) {
this.name = name;
this.age = age;
this.email = email;
}
public String getName() { return name; }
public int getAge() { return age; }
// 返回Optional,明确表示可能没有邮箱
public Optional<String> getEmail() {
return Optional.ofNullable(email);
}
}
class RegistrationRequest {
private String username;
private String fullName;
private String email;
public RegistrationRequest(String username, String fullName, String email) {
this.username = username;
this.fullName = fullName;
this.email = email;
}
public String getUsername() { return username; }
public String getFullName() { return fullName; }
public String getEmail() { return email; }
}
总结
核心要点回顾:
- Stream是惰性的:只有终端操作才会触发实际计算
- 不可重复使用:Stream只能消费一次
- 不影响源数据:Stream操作不会修改原始集合
- 函数式风格:鼓励使用lambda表达式和方法引用
- 并行处理简单:只需将
.stream()改为.parallelStream()
何时使用Stream?
✅ 适合使用Stream:
- 需要对集合进行复杂的过滤、转换、聚合操作
- 处理大量数据,特别是可以并行处理的场景
- 希望编写更声明式、更易读的代码
- 需要链式处理多个数据转换步骤
❌ 不适合使用Stream:
- 简单的遍历操作(传统for循环更直接)
- 需要直接操作索引的场景
- 需要修改原始集合的内容
- 异常处理复杂的场景