Java Stream API 完全指南:从零开始掌握函数式数据处理

41 阅读15分钟

引言:为什么需要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; }
}

总结

核心要点回顾:

  1. Stream是惰性的:只有终端操作才会触发实际计算
  2. 不可重复使用:Stream只能消费一次
  3. 不影响源数据:Stream操作不会修改原始集合
  4. 函数式风格:鼓励使用lambda表达式和方法引用
  5. 并行处理简单:只需将.stream()改为.parallelStream()

何时使用Stream?

✅ 适合使用Stream

  • 需要对集合进行复杂的过滤、转换、聚合操作
  • 处理大量数据,特别是可以并行处理的场景
  • 希望编写更声明式、更易读的代码
  • 需要链式处理多个数据转换步骤

❌ 不适合使用Stream

  • 简单的遍历操作(传统for循环更直接)
  • 需要直接操作索引的场景
  • 需要修改原始集合的内容
  • 异常处理复杂的场景