Java Stream API完全指南
Java 8引入的Stream API是Java函数式编程的重要里程碑,它提供了一种声明式的方式来处理集合数据。Stream API不仅让代码更加简洁、可读,还支持并行处理,大大提升了Java处理数据的能力和开发效率。
核心价值
Stream API = 声明式编程 + 函数式风格 + 并行处理 + 流水线操作
- 📝 声明式编程:关注做什么而非怎么做,提高代码可读性
- λ 函数式风格:使用函数组合和表达式,减少副作用
- ⚡ 并行处理:简单切换并行执行,充分利用多核处理能力
- 🔄 流水线操作:链式API支持多阶段处理,简化复杂数据转换
- 🧩 内置操作:丰富的内置操作符,简化常见数据处理任务
1. Stream基础概念与原理
1.1 Stream核心概念
Stream不是数据结构,而是数据源的视图。它不存储数据,而是按需计算。Stream操作分为中间操作和终端操作。
Stream特性对比表
| 特性 | 传统集合操作 | Stream API | 优势 |
|---|---|---|---|
| 执行方式 | 立即执行 | 惰性求值 | 性能优化,避免不必要计算 |
| 数据修改 | 可能修改原数据 | 不修改原数据 | 数据安全,函数式编程 |
| 并行处理 | 需要手动实现 | 内置并行支持 | 简化并发编程 |
| 代码风格 | 命令式 | 声明式 | 代码更简洁易读 |
| 链式调用 | 不支持 | 完全支持 | 流畅的API体验 |
| 内存使用 | 可能创建中间集合 | 按需处理 | 内存效率更高 |
- Stream基础
- Stream流水线
- 惰性求值
Stream创建与基本操作
Stream创建方式完整示例
java
1import java.util.*;2import java.util.stream.*;3import java.nio.file.*;4import java.io.IOException;56public class StreamCreationDemo {7 public static void main(String[] args) throws IOException {8 // 1. 从集合创建Stream9 List<String> languages = Arrays.asList("Java", "Python", "JavaScript", "C++", "Go");10 Stream<String> streamFromList = languages.stream();11 System.out.println("从List创建: " + streamFromList.collect(Collectors.toList()));12 13 // 2. 从数组创建Stream14 String[] array = {"Apple", "Banana", "Cherry", "Date"};15 Stream<String> streamFromArray = Arrays.stream(array);16 System.out.println("从数组创建: " + streamFromArray.collect(Collectors.toList()));17 18 // 3. 使用Stream.of()创建19 Stream<Integer> streamOf = Stream.of(1, 2, 3, 4, 5);20 System.out.println("使用of()创建: " + streamOf.collect(Collectors.toList()));21 22 // 4. 创建空Stream23 Stream<String> emptyStream = Stream.empty();24 System.out.println("空Stream元素数量: " + emptyStream.count());25 26 // 5. 使用Stream.generate()创建无限流27 Stream<Double> randomStream = Stream.generate(Math::random).limit(5);28 System.out.println("随机数流: " + randomStream.collect(Collectors.toList()));29 30 // 6. 使用Stream.iterate()创建31 Stream<Integer> iterateStream = Stream.iterate(0, n -> n + 2).limit(10);32 System.out.println("迭代流(偶数): " + iterateStream.collect(Collectors.toList()));33 34 // 7. 从范围创建IntStream35 IntStream rangeStream = IntStream.range(1, 6);36 System.out.println("范围流: " + rangeStream.boxed().collect(Collectors.toList()));37 38 // 8. 从文件创建Stream39 try {40 Stream<String> fileStream = Files.lines(Paths.get("example.txt"));41 // 注意:实际使用时需要确保文件存在42 // System.out.println("文件行数: " + fileStream.count());43 } catch (Exception e) {44 System.out.println("文件不存在,跳过文件流示例");45 }46 47 // 9. 从字符串创建字符流48 IntStream charStream = "Hello World".chars();49 System.out.println("字符流: " + charStream.mapToObj(c -> (char) c).collect(Collectors.toList()));50 51 // 10. 并行流创建52 Stream<String> parallelStream = languages.parallelStream();53 System.out.println("并行流处理: " + parallelStream.map(String::toUpperCase).collect(Collectors.toList()));54 }55}Stream操作流水线
Stream操作流水线详解
java
1import java.util.*;2import java.util.stream.*;34public class StreamPipelineDemo {5 6 static class Product {7 private String name;8 private String category;9 private double price;10 private int stock;11 private boolean available;12 13 public Product(String name, String category, double price, int stock, boolean available) {14 this.name = name;15 this.category = category;16 this.price = price;17 this.stock = stock;18 this.available = available;19 }20 21 // getters22 public String getName() { return name; }23 public String getCategory() { return category; }24 public double getPrice() { return price; }25 public int getStock() { return stock; }26 public boolean isAvailable() { return available; }27 28 @Override29 public String toString() {30 return String.format("Product{name='%s', category='%s', price=%.2f, stock=%d, available=%s}",31 name, category, price, stock, available);32 }33 }34 35 public static void main(String[] args) {36 // 创建测试数据37 List<Product> products = Arrays.asList(38 new Product("iPhone 14", "Electronics", 999.99, 50, true),39 new Product("MacBook Pro", "Electronics", 2499.99, 20, true),40 new Product("AirPods", "Electronics", 179.99, 100, true),41 new Product("Java编程思想", "Books", 89.99, 30, true),42 new Product("Spring实战", "Books", 79.99, 0, false),43 new Product("Nike运动鞋", "Sports", 129.99, 80, true),44 new Product("Adidas T恤", "Sports", 49.99, 0, false),45 new Product("咖啡机", "Home", 299.99, 15, true)46 );47 48 System.out.println("=== 原始数据 ===");49 products.forEach(System.out::println);50 51 // 复杂的Stream流水线操作52 System.out.println("\n=== 复杂流水线处理 ===");53 Map<String, List<String>> result = products.stream()54 .filter(product -> product.isAvailable()) // 过滤:只要可用的产品55 .filter(product -> product.getPrice() > 50) // 过滤:价格大于5056 .peek(product -> System.out.println("处理中: " + product.getName())) // 调试:查看处理过程57 .sorted(Comparator.comparing(Product::getPrice).reversed()) // 排序:按价格降序58 .limit(5) // 限制:只取前5个59 .collect(Collectors.groupingBy( // 分组:按类别分组60 Product::getCategory,61 Collectors.mapping(Product::getName, Collectors.toList())62 ));63 64 System.out.println("\n=== 处理结果 ===");65 result.forEach((category, productNames) -> {66 System.out.println(category + ": " + productNames);67 });68 69 // 统计信息70 System.out.println("\n=== 统计信息 ===");71 DoubleSummaryStatistics priceStats = products.stream()72 .filter(Product::isAvailable)73 .mapToDouble(Product::getPrice)74 .summaryStatistics();75 76 System.out.println("价格统计: " + priceStats);77 System.out.println("平均价格: " + String.format("%.2f", priceStats.getAverage()));78 System.out.println("最高价格: " + priceStats.getMax());79 System.out.println("最低价格: " + priceStats.getMin());80 System.out.println("总计: " + String.format("%.2f", priceStats.getSum()));81 82 // 查找操作83 System.out.println("\n=== 查找操作 ===");84 Optional<Product> mostExpensive = products.stream()85 .filter(Product::isAvailable)86 .max(Comparator.comparing(Product::getPrice));87 88 mostExpensive.ifPresent(product -> 89 System.out.println("最贵的产品: " + product.getName() + " - $" + product.getPrice()));90 91 // 检查操作92 boolean hasExpensiveProduct = products.stream()93 .anyMatch(product -> product.getPrice() > 2000);94 System.out.println("是否有价格超过2000的产品: " + hasExpensiveProduct);95 96 boolean allAvailable = products.stream()97 .allMatch(Product::isAvailable);98 System.out.println("所有产品都可用: " + allAvailable);99 }100}惰性求值机制
惰性求值演示
java
1import java.util.*;2import java.util.stream.*;34public class LazyEvaluationDemo {5 6 // 自定义函数,用于演示何时执行7 public static String processString(String input) {8 System.out.println("处理字符串: " + input);9 return input.toUpperCase();10 }11 12 public static boolean isLongString(String input) {13 System.out.println("检查字符串长度: " + input);14 return input.length() > 5;15 }16 17 public static void main(String[] args) {18 List<String> words = Arrays.asList("apple", "banana", "cherry", "date", "elderberry", "fig");19 20 System.out.println("=== 演示惰性求值 ===");21 System.out.println("创建Stream(注意:没有任何输出)");22 23 Stream<String> stream = words.stream()24 .filter(word -> {25 System.out.println("过滤: " + word);26 return word.length() > 4;27 })28 .map(word -> {29 System.out.println("映射: " + word);30 return word.toUpperCase();31 });32 33 System.out.println("Stream已创建,但还没有执行任何操作");34 System.out.println("现在执行终端操作...");35 36 List<String> result = stream.collect(Collectors.toList());37 System.out.println("结果: " + result);38 39 System.out.println("\n=== 短路操作演示 ===");40 // 短路操作:findFirst会在找到第一个匹配元素后停止处理41 Optional<String> firstLong = words.stream()42 .filter(word -> {43 System.out.println("短路过滤: " + word);44 return word.length() > 5;45 })46 .findFirst();47 48 System.out.println("第一个长字符串: " + firstLong.orElse("未找到"));49 50 System.out.println("\n=== limit操作的短路效果 ===");51 List<String> limitedResult = words.stream()52 .filter(word -> {53 System.out.println("limit过滤: " + word);54 return word.length() > 3;55 })56 .limit(2) // 只取前2个57 .collect(Collectors.toList());58 59 System.out.println("限制结果: " + limitedResult);60 61 System.out.println("\n=== 无终端操作的Stream ===");62 // 这个Stream不会执行任何操作,因为没有终端操作63 words.stream()64 .filter(word -> {65 System.out.println("这行不会打印: " + word);66 return true;67 })68 .map(word -> {69 System.out.println("这行也不会打印: " + word);70 return word.toUpperCase();71 });72 73 System.out.println("没有终端操作,所以上面的中间操作都不会执行");74 75 System.out.println("\n=== 性能对比:传统方式 vs Stream ===");76 77 // 传统方式78 long startTime = System.nanoTime();79 List<String> traditionalResult = new ArrayList<>();80 for (String word : words) {81 if (word.length() > 4) {82 traditionalResult.add(word.toUpperCase());83 }84 }85 long traditionalTime = System.nanoTime() - startTime;86 87 // Stream方式88 startTime = System.nanoTime();89 List<String> streamResult = words.stream()90 .filter(word -> word.length() > 4)91 .map(String::toUpperCase)92 .collect(Collectors.toList());93 long streamTime = System.nanoTime() - startTime;94 95 System.out.println("传统方式结果: " + traditionalResult);96 System.out.println("Stream方式结果: " + streamResult);97 System.out.println("传统方式耗时: " + traditionalTime + " 纳秒");98 System.out.println("Stream方式耗时: " + streamTime + " 纳秒");99 }100}2. Stream中间操作详解
中间操作是Stream处理的核心,它们是惰性的,只有在遇到终端操作时才会执行。
- 过滤操作
- 映射操作
- 排序去重
过滤操作详解
过滤操作完整示例
java
1import java.util.*;2import java.util.stream.*;34public class FilterOperationsDemo {5 6 static class Employee {7 private String name;8 private String department;9 private int age;10 private double salary;11 private List<String> skills;12 13 public Employee(String name, String department, int age, double salary, String... skills) {14 this.name = name;15 this.department = department;16 this.age = age;17 this.salary = salary;18 this.skills = Arrays.asList(skills);19 }20 21 // getters22 public String getName() { return name; }23 public String getDepartment() { return department; }24 public int getAge() { return age; }25 public double getSalary() { return salary; }26 public List<String> getSkills() { return skills; }27 28 @Override29 public String toString() {30 return String.format("Employee{name='%s', dept='%s', age=%d, salary=%.0f, skills=%s}",31 name, department, age, salary, skills);32 }33 }34 35 public static void main(String[] args) {36 List<Employee> employees = Arrays.asList(37 new Employee("张三", "IT", 28, 8000, "Java", "Spring", "MySQL"),38 new Employee("李四", "IT", 32, 12000, "Python", "Django", "PostgreSQL"),39 new Employee("王五", "HR", 29, 6000, "招聘", "培训"),40 new Employee("赵六", "Finance", 35, 9000, "会计", "审计"),41 new Employee("钱七", "IT", 26, 7000, "JavaScript", "React", "Node.js"),42 new Employee("孙八", "Marketing", 31, 7500, "市场分析", "广告策划"),43 new Employee("周九", "IT", 40, 15000, "Java", "架构设计", "团队管理")44 );45 46 System.out.println("=== 原始员工数据 ===");47 employees.forEach(System.out::println);48 49 // 1. 基础过滤 - filter()50 System.out.println("\n=== IT部门员工 ===");51 List<Employee> itEmployees = employees.stream()52 .filter(emp -> "IT".equals(emp.getDepartment()))53 .collect(Collectors.toList());54 itEmployees.forEach(System.out::println);55 56 // 2. 复合条件过滤57 System.out.println("\n=== IT部门且薪资>8000的员工 ===");58 List<Employee> highSalaryIT = employees.stream()59 .filter(emp -> "IT".equals(emp.getDepartment()))60 .filter(emp -> emp.getSalary() > 8000)61 .collect(Collectors.toList());62 highSalaryIT.forEach(System.out::println);63 64 // 3. 使用Predicate组合65 System.out.println("\n=== 使用Predicate组合条件 ===");66 Predicate<Employee> isIT = emp -> "IT".equals(emp.getDepartment());67 Predicate<Employee> isHighSalary = emp -> emp.getSalary() > 10000;68 Predicate<Employee> isYoung = emp -> emp.getAge() < 30;69 70 // 年轻的高薪IT员工71 List<Employee> youngHighSalaryIT = employees.stream()72 .filter(isIT.and(isHighSalary).and(isYoung))73 .collect(Collectors.toList());74 System.out.println("年轻高薪IT员工: " + youngHighSalaryIT);75 76 // IT或高薪员工77 List<Employee> itOrHighSalary = employees.stream()78 .filter(isIT.or(isHighSalary))79 .collect(Collectors.toList());80 System.out.println("IT或高薪员工数量: " + itOrHighSalary.size());81 82 // 4. 基于集合属性过滤83 System.out.println("\n=== 掌握Java技能的员工 ===");84 List<Employee> javaEmployees = employees.stream()85 .filter(emp -> emp.getSkills().contains("Java"))86 .collect(Collectors.toList());87 javaEmployees.forEach(System.out::println);88 89 // 5. 空值过滤90 List<String> namesWithNulls = Arrays.asList("Alice", null, "Bob", "", "Charlie", null);91 System.out.println("\n=== 过滤空值和空字符串 ===");92 List<String> validNames = namesWithNulls.stream()93 .filter(Objects::nonNull)94 .filter(name -> !name.trim().isEmpty())95 .collect(Collectors.toList());96 System.out.println("有效姓名: " + validNames);97 98 // 6. 范围过滤99 System.out.println("\n=== 年龄在25-35之间的员工 ===");100 List<Employee> ageRangeEmployees = employees.stream()101 .filter(emp -> emp.getAge() >= 25 && emp.getAge() <= 35)102 .collect(Collectors.toList());103 ageRangeEmployees.forEach(emp -> 104 System.out.println(emp.getName() + " - 年龄: " + emp.getAge()));105 106 // 7. 自定义复杂过滤逻辑107 System.out.println("\n=== 资深员工(年龄>30且薪资>平均薪资) ===");108 double avgSalary = employees.stream()109 .mapToDouble(Employee::getSalary)110 .average()111 .orElse(0.0);112 113 System.out.println("平均薪资: " + String.format("%.2f", avgSalary));114 115 List<Employee> seniorEmployees = employees.stream()116 .filter(emp -> emp.getAge() > 30 && emp.getSalary() > avgSalary)117 .collect(Collectors.toList());118 seniorEmployees.forEach(System.out::println);119 }120}映射操作详解
映射操作完整示例
java
1import java.util.*;2import java.util.stream.*;34public class MapOperationsDemo {5 6 static class Order {7 private String orderId;8 private String customerId;9 private List<OrderItem> items;10 private double totalAmount;11 12 public Order(String orderId, String customerId, double totalAmount, OrderItem... items) {13 this.orderId = orderId;14 this.customerId = customerId;15 this.totalAmount = totalAmount;16 this.items = Arrays.asList(items);17 }18 19 // getters20 public String getOrderId() { return orderId; }21 public String getCustomerId() { return customerId; }22 public List<OrderItem> getItems() { return items; }23 public double getTotalAmount() { return totalAmount; }24 25 @Override26 public String toString() {27 return String.format("Order{id='%s', customer='%s', total=%.2f, items=%d}",28 orderId, customerId, totalAmount, items.size());29 }30 }31 32 static class OrderItem {33 private String productName;34 private int quantity;35 private double price;36 37 public OrderItem(String productName, int quantity, double price) {38 this.productName = productName;39 this.quantity = quantity;40 this.price = price;41 }42 43 // getters44 public String getProductName() { return productName; }45 public int getQuantity() { return quantity; }46 public double getPrice() { return price; }47 48 @Override49 public String toString() {50 return String.format("OrderItem{product='%s', qty=%d, price=%.2f}",51 productName, quantity, price);52 }53 }54 55 public static void main(String[] args) {56 // 创建测试数据57 List<Order> orders = Arrays.asList(58 new Order("ORD001", "CUST001", 299.99,59 new OrderItem("笔记本电脑", 1, 199.99),60 new OrderItem("鼠标", 2, 50.00)),61 new Order("ORD002", "CUST002", 89.98,62 new OrderItem("键盘", 1, 79.99),63 new OrderItem("鼠标垫", 1, 9.99)),64 new Order("ORD003", "CUST001", 1299.99,65 new OrderItem("显示器", 1, 899.99),66 new OrderItem("音响", 1, 400.00))67 );68 69 System.out.println("=== 原始订单数据 ===");70 orders.forEach(System.out::println);71 72 // 1. 基础映射 - map()73 System.out.println("\n=== 提取订单ID ===");74 List<String> orderIds = orders.stream()75 .map(Order::getOrderId)76 .collect(Collectors.toList());77 System.out.println("订单ID列表: " + orderIds);78 79 // 2. 映射到不同类型80 System.out.println("\n=== 订单总金额列表 ===");81 List<Double> totalAmounts = orders.stream()82 .map(Order::getTotalAmount)83 .collect(Collectors.toList());84 System.out.println("总金额列表: " + totalAmounts);85 86 // 3. 复杂对象映射87 System.out.println("\n=== 创建订单摘要 ===");88 List<String> orderSummaries = orders.stream()89 .map(order -> String.format("订单%s: 客户%s, 金额%.2f, 商品%d件",90 order.getOrderId(),91 order.getCustomerId(),92 order.getTotalAmount(),93 order.getItems().size()))94 .collect(Collectors.toList());95 orderSummaries.forEach(System.out::println);96 97 // 4. flatMap - 扁平化操作98 System.out.println("\n=== 所有订单项(使用flatMap) ===");99 List<OrderItem> allItems = orders.stream()100 .flatMap(order -> order.getItems().stream())101 .collect(Collectors.toList());102 allItems.forEach(System.out::println);103 104 // 5. flatMap复杂示例 - 提取所有产品名称105 System.out.println("\n=== 所有产品名称(去重) ===");106 List<String> allProductNames = orders.stream()107 .flatMap(order -> order.getItems().stream())108 .map(OrderItem::getProductName)109 .distinct()110 .collect(Collectors.toList());111 System.out.println("所有产品: " + allProductNames);112 113 // 6. mapToInt/mapToDouble - 映射到基本类型114 System.out.println("\n=== 数值统计 ===");115 IntSummaryStatistics itemStats = orders.stream()116 .flatMap(order -> order.getItems().stream())117 .mapToInt(OrderItem::getQuantity)118 .summaryStatistics();119 120 System.out.println("商品数量统计: " + itemStats);121 System.out.println("总商品数量: " + itemStats.getSum());122 System.out.println("平均数量: " + itemStats.getAverage());123 124 DoubleSummaryStatistics priceStats = orders.stream()125 .mapToDouble(Order::getTotalAmount)126 .summaryStatistics();127 128 System.out.println("订单金额统计: " + priceStats);129 System.out.println("总金额: " + String.format("%.2f", priceStats.getSum()));130 System.out.println("平均金额: " + String.format("%.2f", priceStats.getAverage()));131 132 // 7. 链式映射操作133 System.out.println("\n=== 链式映射:客户订单统计 ===");134 Map<String, Double> customerTotals = orders.stream()135 .collect(Collectors.groupingBy(136 Order::getCustomerId,137 Collectors.summingDouble(Order::getTotalAmount)138 ));139 140 List<String> customerSummaries = customerTotals.entrySet().stream()141 .map(entry -> String.format("客户%s: 总消费%.2f", entry.getKey(), entry.getValue()))142 .sorted()143 .collect(Collectors.toList());144 145 customerSummaries.forEach(System.out::println);146 147 // 8. 条件映射148 System.out.println("\n=== 条件映射:订单等级 ===");149 List<String> orderLevels = orders.stream()150 .map(order -> {151 String level;152 if (order.getTotalAmount() > 1000) {153 level = "VIP";154 } else if (order.getTotalAmount() > 200) {155 level = "高级";156 } else {157 level = "普通";158 }159 return order.getOrderId() + " - " + level;160 })161 .collect(Collectors.toList());162 163 orderLevels.forEach(System.out::println);164 165 // 9. 映射到Optional166 System.out.println("\n=== 安全映射示例 ===");167 List<String> safeCustomerIds = orders.stream()168 .map(order -> Optional.ofNullable(order.getCustomerId())169 .filter(id -> !id.isEmpty())170 .orElse("UNKNOWN"))171 .collect(Collectors.toList());172 173 System.out.println("安全的客户ID: " + safeCustomerIds);174 175 // 10. 自定义映射函数176 System.out.println("\n=== 自定义映射函数 ===");177 Function<Order, String> orderFormatter = order -> 178 String.format("[%s] %s (%.2f元)", 179 order.getOrderId(), 180 order.getCustomerId(), 181 order.getTotalAmount());182 183 List<String> formattedOrders = orders.stream()184 .map(orderFormatter)185 .collect(Collectors.toList());186 187 formattedOrders.forEach(System.out::println);188 }189}排序和去重操作
排序去重操作完整示例
java
1import java.util.*;2import java.util.stream.*;34public class SortDistinctDemo {5 6 static class Student {7 private String name;8 private int age;9 private String major;10 private double gpa;11 private List<String> courses;12 13 public Student(String name, int age, String major, double gpa, String... courses) {14 this.name = name;15 this.age = age;16 this.major = major;17 this.gpa = gpa;18 this.courses = Arrays.asList(courses);19 }20 21 // getters22 public String getName() { return name; }23 public int getAge() { return age; }24 public String getMajor() { return major; }25 public double getGpa() { return gpa; }26 public List<String> getCourses() { return courses; }27 28 @Override29 public String toString() {30 return String.format("Student{name='%s', age=%d, major='%s', gpa=%.2f}",31 name, age, major, gpa);32 }33 34 @Override35 public boolean equals(Object obj) {36 if (this == obj) return true;37 if (obj == null || getClass() != obj.getClass()) return false;38 Student student = (Student) obj;39 return Objects.equals(name, student.name) && Objects.equals(major, student.major);40 }41 42 @Override43 public int hashCode() {44 return Objects.hash(name, major);45 }46 }47 48 public static void main(String[] args) {49 List<Student> students = Arrays.asList(50 new Student("张三", 20, "计算机科学", 3.8, "数据结构", "算法", "数据库"),51 new Student("李四", 22, "软件工程", 3.6, "软件架构", "设计模式"),52 new Student("王五", 19, "计算机科学", 3.9, "操作系统", "网络编程"),53 new Student("赵六", 21, "信息管理", 3.4, "信息系统", "数据分析"),54 new Student("钱七", 20, "软件工程", 3.7, "项目管理", "软件测试"),55 new Student("张三", 20, "计算机科学", 3.8, "数据结构", "算法"), // 重复数据56 new Student("孙八", 23, "计算机科学", 3.5, "人工智能", "机器学习"),57 new Student("周九", 22, "信息管理", 3.2, "数据挖掘", "商业智能")58 );59 60 System.out.println("=== 原始学生数据 ===");61 students.forEach(System.out::println);62 63 // 1. 基础排序 - 按年龄排序64 System.out.println("\n=== 按年龄升序排序 ===");65 List<Student> sortedByAge = students.stream()66 .sorted(Comparator.comparing(Student::getAge))67 .collect(Collectors.toList());68 sortedByAge.forEach(System.out::println);69 70 // 2. 降序排序 - 按GPA降序71 System.out.println("\n=== 按GPA降序排序 ===");72 List<Student> sortedByGpaDesc = students.stream()73 .sorted(Comparator.comparing(Student::getGpa).reversed())74 .collect(Collectors.toList());75 sortedByGpaDesc.forEach(System.out::println);76 77 // 3. 多字段排序78 System.out.println("\n=== 多字段排序:专业升序,GPA降序 ===");79 List<Student> multiFieldSorted = students.stream()80 .sorted(Comparator.comparing(Student::getMajor)81 .thenComparing(Student::getGpa, Comparator.reverseOrder()))82 .collect(Collectors.toList());83 multiFieldSorted.forEach(System.out::println);84 85 // 4. 自定义排序逻辑86 System.out.println("\n=== 自定义排序:年龄分组排序 ===");87 List<Student> customSorted = students.stream()88 .sorted((s1, s2) -> {89 // 先按年龄分组:20岁以下、20-22岁、22岁以上90 int ageGroup1 = s1.getAge() < 20 ? 0 : (s1.getAge() <= 22 ? 1 : 2);91 int ageGroup2 = s2.getAge() < 20 ? 0 : (s2.getAge() <= 22 ? 1 : 2);92 93 if (ageGroup1 != ageGroup2) {94 return Integer.compare(ageGroup1, ageGroup2);95 }96 97 // 同一年龄组内按GPA降序98 return Double.compare(s2.getGpa(), s1.getGpa());99 })100 .collect(Collectors.toList());101 102 customSorted.forEach(student -> 103 System.out.println(String.format("年龄组: %s, %s", 104 getAgeGroup(student.getAge()), student)));105 106 // 5. 去重操作 - distinct()107 System.out.println("\n=== 去重操作 ===");108 List<Student> distinctStudents = students.stream()109 .distinct()110 .collect(Collectors.toList());111 System.out.println("去重前数量: " + students.size());112 System.out.println("去重后数量: " + distinctStudents.size());113 distinctStudents.forEach(System.out::println);114 115 // 6. 基于特定字段去重116 System.out.println("\n=== 基于专业去重(保留GPA最高的) ===");117 Map<String, Student> bestByMajor = students.stream()118 .collect(Collectors.toMap(119 Student::getMajor,120 student -> student,121 (existing, replacement) -> 122 existing.getGpa() > replacement.getGpa() ? existing : replacement123 ));124 125 bestByMajor.values().forEach(System.out::println);126 127 // 7. 复杂去重:基于多个字段128 System.out.println("\n=== 基于姓名和年龄去重 ===");129 List<Student> distinctByNameAge = students.stream()130 .collect(Collectors.toMap(131 student -> student.getName() + "-" + student.getAge(),132 student -> student,133 (existing, replacement) -> existing134 ))135 .values()136 .stream()137 .sorted(Comparator.comparing(Student::getName))138 .collect(Collectors.toList());139 140 distinctByNameAge.forEach(System.out::println);141 142 // 8. 排序 + 去重组合143 System.out.println("\n=== 排序后去重:按专业分组,每组取GPA最高的2名 ===");144 Map<String, List<Student>> topStudentsByMajor = students.stream()145 .distinct()146 .sorted(Comparator.comparing(Student::getGpa).reversed())147 .collect(Collectors.groupingBy(Student::getMajor))148 .entrySet().stream()149 .collect(Collectors.toMap(150 Map.Entry::getKey,151 entry -> entry.getValue().stream().limit(2).collect(Collectors.toList())152 ));153 154 topStudentsByMajor.forEach((major, topStudents) -> {155 System.out.println(major + "专业前2名:");156 topStudents.forEach(student -> System.out.println(" " + student));157 });158 159 // 9. 字符串去重和排序160 System.out.println("\n=== 所有课程去重排序 ===");161 List<String> allCourses = students.stream()162 .flatMap(student -> student.getCourses().stream())163 .distinct()164 .sorted()165 .collect(Collectors.toList());166 167 System.out.println("所有课程: " + allCourses);168 169 // 10. 性能对比:排序 vs 去重顺序170 System.out.println("\n=== 性能测试:先排序后去重 vs 先去重后排序 ===");171 172 // 创建大量重复数据进行测试173 List<Student> largeDataset = new ArrayList<>();174 for (int i = 0; i < 1000; i++) {175 largeDataset.addAll(students);176 }177 178 // 先排序后去重179 long startTime = System.nanoTime();180 List<Student> sortThenDistinct = largeDataset.stream()181 .sorted(Comparator.comparing(Student::getGpa))182 .distinct()183 .collect(Collectors.toList());184 long sortFirstTime = System.nanoTime() - startTime;185 186 // 先去重后排序187 startTime = System.nanoTime();188 List<Student> distinctThenSort = largeDataset.stream()189 .distinct()190 .sorted(Comparator.comparing(Student::getGpa))191 .collect(Collectors.toList());192 long distinctFirstTime = System.nanoTime() - startTime;193 194 System.out.println("数据量: " + largeDataset.size());195 System.out.println("先排序后去重耗时: " + sortFirstTime / 1_000_000 + " ms");196 System.out.println("先去重后排序耗时: " + distinctFirstTime / 1_000_000 + " ms");197 System.out.println("结果数量相同: " + (sortThenDistinct.size() == distinctThenSort.size()));198 }199 200 private static String getAgeGroup(int age) {201 if (age < 20) return "19岁以下";202 if (age <= 22) return "20-22岁";203 return "22岁以上";204 }205}3. Stream终端操作详解
终端操作会触发流的处理并产生结果。一旦执行了终端操作,流就被消费了,不能再次使用。
- 并行流与性能优化
并行流是Stream API的重要特性,可以充分利用多核处理器的优势。
并行流使用注意事项
- 数据量要足够大:小数据集使用并行流可能反而降低性能
- 避免有状态操作:如sorted、distinct等操作会影响并行性能
- 线程安全:确保Lambda表达式中的操作是线程安全的
- I/O密集型任务:不适合使用并行流,应该使用异步处理
- 并行流基础
- 性能优化
并行流基础操作
并行流基础示例
java
1import java.util.*;2import java.util.concurrent.*;3import java.util.stream.*;45public class ParallelStreamBasics {6 7 public static void main(String[] args) {8 // 创建大量测试数据9 List<Integer> numbers = IntStream.rangeClosed(1, 1_000_000)10 .boxed()11 .collect(Collectors.toList());12 13 System.out.println("数据量: " + numbers.size());14 System.out.println("可用处理器数量: " + Runtime.getRuntime().availableProcessors());15 16 // 1. 串行流 vs 并行流性能对比17 System.out.println("\n=== 性能对比:计算平方和 ===");18 19 // 串行流20 long startTime = System.currentTimeMillis();21 long serialSum = numbers.stream()22 .mapToLong(n -> (long) n * n)23 .sum();24 long serialTime = System.currentTimeMillis() - startTime;25 26 // 并行流27 startTime = System.currentTimeMillis();28 long parallelSum = numbers.parallelStream()29 .mapToLong(n -> (long) n * n)30 .sum();31 long parallelTime = System.currentTimeMillis() - startTime;32 33 System.out.println("串行结果: " + serialSum + ", 耗时: " + serialTime + "ms");34 System.out.println("并行结果: " + parallelSum + ", 耗时: " + parallelTime + "ms");35 System.out.println("性能提升: " + String.format("%.2fx", (double) serialTime / parallelTime));36 37 // 2. 并行流创建方式38 System.out.println("\n=== 并行流创建方式 ===");39 40 // 方式1: 从集合创建并行流41 Stream<Integer> parallelStream1 = numbers.parallelStream();42 43 // 方式2: 将串行流转换为并行流44 Stream<Integer> parallelStream2 = numbers.stream().parallel();45 46 // 方式3: 直接创建并行流47 IntStream parallelIntStream = IntStream.range(1, 1000).parallel();48 49 System.out.println("并行流1是否并行: " + parallelStream1.isParallel());50 System.out.println("并行流2是否并行: " + parallelStream2.isParallel());51 System.out.println("并行IntStream是否并行: " + parallelIntStream.isParallel());52 53 // 3. 并行流转串行流54 Stream<Integer> sequentialStream = numbers.parallelStream().sequential();55 System.out.println("转换后是否并行: " + sequentialStream.isParallel());56 57 // 4. 复杂计算的并行处理58 System.out.println("\n=== 复杂计算并行处理 ===");59 60 // 模拟复杂计算函数61 Function<Integer, Double> complexCalculation = n -> {62 double result = 0;63 for (int i = 1; i <= 1000; i++) {64 result += Math.sin(n * i) * Math.cos(n * i);65 }66 return result;67 };68 69 List<Integer> smallDataset = IntStream.rangeClosed(1, 1000)70 .boxed()71 .collect(Collectors.toList());72 73 // 串行复杂计算74 startTime = System.currentTimeMillis();75 double serialComplexSum = smallDataset.stream()76 .mapToDouble(complexCalculation::apply)77 .sum();78 long serialComplexTime = System.currentTimeMillis() - startTime;79 80 // 并行复杂计算81 startTime = System.currentTimeMillis();82 double parallelComplexSum = smallDataset.parallelStream()83 .mapToDouble(complexCalculation::apply)84 .sum();85 long parallelComplexTime = System.currentTimeMillis() - startTime;86 87 System.out.println("串行复杂计算: " + String.format("%.2f", serialComplexSum) + 88 ", 耗时: " + serialComplexTime + "ms");89 System.out.println("并行复杂计算: " + String.format("%.2f", parallelComplexSum) + 90 ", 耗时: " + parallelComplexTime + "ms");91 System.out.println("复杂计算性能提升: " + 92 String.format("%.2fx", (double) serialComplexTime / parallelComplexTime));93 }94}并行流性能优化
并行流性能优化实践
java
1import java.util.*;2import java.util.concurrent.*;3import java.util.stream.*;45public class ParallelStreamOptimization {6 7 // 自定义ForkJoinPool8 private static final ForkJoinPool customThreadPool = new ForkJoinPool(8);9 10 public static void main(String[] args) {11 // 1. 数据量对并行性能的影响12 System.out.println("=== 数据量对并行性能的影响 ===");13 testDataSizeImpact();14 15 // 2. 操作类型对并行性能的影响16 System.out.println("\n=== 操作类型对并行性能的影响 ===");17 testOperationTypeImpact();18 19 // 3. 自定义线程池20 System.out.println("\n=== 自定义线程池 ===");21 testCustomThreadPool();22 23 // 4. 并行流的陷阱24 System.out.println("\n=== 并行流的陷阱 ===");25 demonstrateParallelPitfalls();26 27 // 清理资源28 customThreadPool.shutdown();29 }30 31 private static void testDataSizeImpact() {32 int[] dataSizes = {1_000, 10_000, 100_000, 1_000_000};33 34 for (int size : dataSizes) {35 List<Integer> data = IntStream.rangeClosed(1, size)36 .boxed()37 .collect(Collectors.toList());38 39 // 串行处理40 long startTime = System.nanoTime();41 long serialSum = data.stream()42 .mapToLong(n -> (long) n * n)43 .sum();44 long serialTime = System.nanoTime() - startTime;45 46 // 并行处理47 startTime = System.nanoTime();48 long parallelSum = data.parallelStream()49 .mapToLong(n -> (long) n * n)50 .sum();51 long parallelTime = System.nanoTime() - startTime;52 53 double speedup = (double) serialTime / parallelTime;54 55 System.out.printf("数据量: %,d, 串行: %dms, 并行: %dms, 加速比: %.2fx%n",56 size, serialTime / 1_000_000, parallelTime / 1_000_000, speedup);57 }58 }59 60 private static void testOperationTypeImpact() {61 List<Integer> data = IntStream.rangeClosed(1, 100_000)62 .boxed()63 .collect(Collectors.toList());64 65 // 测试不同类型的操作66 Map<String, Runnable> operations = new LinkedHashMap<>();67 68 // CPU密集型操作69 operations.put("CPU密集型", () -> {70 long serialTime = measureTime(() -> 71 data.stream().mapToDouble(n -> Math.sin(n) * Math.cos(n)).sum());72 long parallelTime = measureTime(() -> 73 data.parallelStream().mapToDouble(n -> Math.sin(n) * Math.cos(n)).sum());74 75 System.out.printf(" 串行: %dms, 并行: %dms, 加速比: %.2fx%n",76 serialTime, parallelTime, (double) serialTime / parallelTime);77 });78 79 // 简单操作80 operations.put("简单操作", () -> {81 long serialTime = measureTime(() -> 82 data.stream().mapToLong(n -> n * 2L).sum());83 long parallelTime = measureTime(() -> 84 data.parallelStream().mapToLong(n -> n * 2L).sum());85 86 System.out.printf(" 串行: %dms, 并行: %dms, 加速比: %.2fx%n",87 serialTime, parallelTime, (double) serialTime / parallelTime);88 });89 90 // 有状态操作91 operations.put("有状态操作", () -> {92 long serialTime = measureTime(() -> 93 data.stream().sorted().limit(1000).count());94 long parallelTime = measureTime(() -> 95 data.parallelStream().sorted().limit(1000).count());96 97 System.out.printf(" 串行: %dms, 并行: %dms, 加速比: %.2fx%n",98 serialTime, parallelTime, (double) serialTime / parallelTime);99 });100 101 operations.forEach((name, operation) -> {102 System.out.println(name + ":");103 operation.run();104 });105 }106 107 private static void testCustomThreadPool() {108 List<Integer> data = IntStream.rangeClosed(1, 100_000)109 .boxed()110 .collect(Collectors.toList());111 112 // 默认ForkJoinPool113 long defaultTime = measureTime(() -> 114 data.parallelStream()115 .mapToDouble(n -> Math.sin(n) * Math.cos(n))116 .sum());117 118 // 自定义ForkJoinPool119 long customTime = measureTime(() -> {120 try {121 return customThreadPool.submit(() ->122 data.parallelStream()123 .mapToDouble(n -> Math.sin(n) * Math.cos(n))124 .sum()125 ).get();126 } catch (Exception e) {127 throw new RuntimeException(e);128 }129 });130 131 System.out.println("默认线程池 (并行度: " + 132 ForkJoinPool.commonPool().getParallelism() + "): " + defaultTime + "ms");133 System.out.println("自定义线程池 (并行度: " + 134 customThreadPool.getParallelism() + "): " + customTime + "ms");135 }136 137 private static void demonstrateParallelPitfalls() {138 List<Integer> data = IntStream.rangeClosed(1, 10_000)139 .boxed()140 .collect(Collectors.toList());141 142 // 陷阱1: 线程安全问题143 System.out.println("陷阱1: 线程安全问题");144 List<Integer> unsafeList = new ArrayList<>(); // 非线程安全145 List<Integer> safeList = Collections.synchronizedList(new ArrayList<>()); // 线程安全146 147 // 不安全的并行操作148 data.parallelStream()149 .filter(n -> n % 2 == 0)150 .forEach(unsafeList::add); // 可能导致数据丢失或异常151 152 // 安全的并行操作153 data.parallelStream()154 .filter(n -> n % 2 == 0)155 .forEach(safeList::add);156 157 System.out.println("不安全列表大小: " + unsafeList.size() + " (可能不正确)");158 System.out.println("安全列表大小: " + safeList.size());159 System.out.println("预期大小: " + data.stream().mapToInt(n -> n % 2 == 0 ? 1 : 0).sum());160 161 // 陷阱2: 装箱/拆箱开销162 System.out.println("\n陷阱2: 装箱/拆箱开销");163 164 // 使用包装类型(有装箱开销)165 long boxedTime = measureTime(() -> 166 data.parallelStream()167 .map(n -> n * n)168 .reduce(0, Integer::sum));169 170 // 使用基本类型(无装箱开销)171 long primitiveTime = measureTime(() -> 172 data.parallelStream()173 .mapToInt(n -> n * n)174 .sum());175 176 System.out.println("包装类型耗时: " + boxedTime + "ms");177 System.out.println("基本类型耗时: " + primitiveTime + "ms");178 System.out.println("性能差异: " + String.format("%.2fx", (double) boxedTime / primitiveTime));179 180 // 陷阱3: 错误的归约操作181 System.out.println("\n陷阱3: 错误的归约操作");182 183 // 错误的归约(非结合律)184 String wrongResult = data.parallelStream()185 .limit(10)186 .map(String::valueOf)187 .reduce("", (a, b) -> a + "-" + b); // 结果不确定188 189 // 正确的归约190 String correctResult = data.parallelStream()191 .limit(10)192 .map(String::valueOf)193 .collect(Collectors.joining("-"));194 195 System.out.println("错误归约结果: " + wrongResult);196 System.out.println("正确归约结果: " + correctResult);197 }198 199 private static long measureTime(Runnable operation) {200 long startTime = System.currentTimeMillis();201 operation.run();202 return System.currentTimeMillis() - startTime;203 }204 205 private static long measureTime(Supplier<Object> operation) {206 long startTime = System.currentTimeMillis();207 operation.get();208 return System.currentTimeMillis() - startTime;209 }210}5. Stream最佳实践与设计模式
掌握Stream的最佳实践和常见设计模式,能够写出更优雅、高效的代码。
最佳实践原则
- 优先使用Stream API:相比传统循环,Stream更简洁易读
- 合理选择并行流:只在数据量大且CPU密集时使用
- 避免副作用:Lambda表达式应该是纯函数
- 使用方法引用:提高代码可读性
- 链式调用要适度:过长的链式调用影响可读性
通过掌握Stream API的这些核心概念和最佳实践,你可以编写出更加简洁、高效、易维护的Java代码,充分发挥函数式编程的优势。
6. 面试题精选
6.1 什么是Stream API?它有什么优势?
答案: Stream API是Java 8引入的处理集合的API,它允许以声明式方式处理数据集合,支持函数式编程范式。
Stream API的主要优势:
- 声明式编程:关注"做什么"而非"怎么做",代码更加简洁、可读
- 函数式风格:支持Lambda表达式和方法引用,减少样板代码
- 并行处理:轻松切换并行/串行执行,充分利用多核处理能力
- 惰性求值:中间操作延迟执行,只有在终端操作时才会执行,提高性能
- 链式操作:支持多步骤流水线处理,API设计流畅
- 内置丰富操作:提供filter、map、reduce等多种数据处理操作
- 无副作用:不修改数据源,符合函数式编程原则
6.2 Stream的中间操作和终端操作有什么区别?常用的操作有哪些?
答案:
- 中间操作(Intermediate Operations):返回一个新的Stream,可以链式调用多个中间操作,不会立即执行
- 终端操作(Terminal Operations):触发实际计算,返回一个非Stream的结果,执行后Stream不能再被使用
常用中间操作:
filter(Predicate): 过滤元素map(Function): 转换元素flatMap(Function): 扁平化嵌套集合distinct(): 去除重复sorted(): 排序peek(Consumer): 查看元素(调试)limit(n): 限制元素数量skip(n): 跳过元素
常用终端操作:
collect(Collector): 收集结果到容器forEach(Consumer): 遍历每个元素reduce(BinaryOperator): 归约操作count(): 计数anyMatch(Predicate): 是否存在匹配元素allMatch(Predicate): 是否全部匹配noneMatch(Predicate): 是否全部不匹配findFirst()/findAny(): 查找元素min()/max(): 查找最小/最大值
java
1// 中间操作示例2List<String> result = names.stream() // 创建Stream3 .filter(name -> name.length() > 5) // 中间操作14 .map(String::toUpperCase) // 中间操作25 .sorted() // 中间操作36 .collect(Collectors.toList()); // 终端操作6.3 Stream与Collection的区别是什么?
答案: Stream与Collection的主要区别:
| 特性 | Stream | Collection |
|---|---|---|
| 目的 | 处理数据 | 存储数据 |
| 数据存储 | 不存储数据 | 存储数据 |
| 执行方式 | 惰性计算 | 立即计算 |
| 消费性 | 只能遍历一次 | 可以多次遍历 |
| 数据修改 | 不修改数据源 | 可以修改数据 |
| 并行处理 | 内置支持并行 | 需手动实现并行 |
| 无限大小 | 可以表示无限序列 | 总是有限大小 |
6.4 如何正确使用并行流(parallelStream)?什么情况下应该避免使用?
答案:
正确使用并行流的原则:
- 数据量足够大:对于小数据集,并行处理的开销可能超过收益
- 操作足够重:CPU密集型操作更适合并行处理
- 数据结构易拆分:ArrayList、数组分解高效,LinkedList分解效率低
- 避免共享可变状态:并行操作中避免修改共享变量
- 合理设置线程池大小:根据CPU核心数和任务特性调整并行度
java
1// 并行流示例2long sum = IntStream.rangeClosed(1, 10_000_000)3 .parallel() // 切换到并行模式4 .filter(n -> n % 2 == 0)5 .mapToLong(n -> n * n)6 .sum();78// 自定义并行度9System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "8");避免使用并行流的场景:
- I/O绑定操作:如文件读写、网络操作
- 顺序敏感操作:结果依赖于元素处理顺序
- 很小的数据量:并行开销可能大于收益(通常低于1万元素)
- 不可分割的数据源:如LinkedList等难以有效分割的结构
- 使用非线程安全的操作或收集器:如普通ArrayList::add
6.5 请解释Stream中的Collector收集器,并列举几个常用的收集器。
答案: Collector是一种终端操作,用于将Stream中的元素累积到结果容器中,如List、Map等。Collectors类提供了多种预定义的收集器。
常用收集器:
- 转换为集合:
java
1// 转为List2List<String> list = stream.collect(Collectors.toList());3// 转为Set4Set<String> set = stream.collect(Collectors.toSet());5// 转为特定集合6TreeSet<String> treeSet = stream.collect(Collectors.toCollection(TreeSet::new));- 字符串连接:
java
1// 简单连接2String joined = stream.collect(Collectors.joining());3// 带分隔符4String joined = stream.collect(Collectors.joining(", "));5// 带前缀和后缀6String joined = stream.collect(Collectors.joining(", ", "[", "]"));- 分组和分区:
java
1// 按属性分组2Map<Department, List<Employee>> byDept = employees.stream()3 .collect(Collectors.groupingBy(Employee::getDepartment));4// 多级分组5Map<Department, Map<EmployeeType, List<Employee>>> byDeptAndType = employees.stream()6 .collect(Collectors.groupingBy(Employee::getDepartment,7 Collectors.groupingBy(Employee::getType)));8// 分区(特殊分组)9Map<Boolean, List<Employee>> partitioned = employees.stream()10 .collect(Collectors.partitioningBy(emp -> emp.getSalary() > 50000));- 统计和汇总:
java
1// 求和2int total = stream.collect(Collectors.summingInt(User::getAge));3// 平均值4double avg = stream.collect(Collectors.averagingInt(User::getAge));5// 汇总统计6IntSummaryStatistics stats = stream.collect(Collectors.summarizingInt(User::getAge));- 归约和映射:
java
1// 归约(类似reduce)2Optional<User> oldest = users.stream()3 .collect(Collectors.reducing((u1, u2) -> u1.getAge() > u2.getAge() ? u1 : u2));4// 映射后收集5Map<Integer, String> idToName = users.stream()6 .collect(Collectors.toMap(User::getId, User::getName));自定义收集器:可以通过实现Collector接口或使用Collector.of()方法创建自定义收集器,满足特殊需求。
评论