想让代码更简洁、高效、优雅?Lambda表达式与Stream流操作完全详解
前言
Java 8 是 Java 语言发展史上最重要的版本更新之一,其中Lambda表达式和Stream流是两大核心重磅特性。二者结合彻底改变了 Java 集合、数组的遍历、筛选、转换、聚合等操作方式,告别了繁琐的匿名内部类、循环遍历模板代码,让代码更简洁、优雅、高效,同时支持函数式编程思想。
本文将从零讲解 Lambda 表达式的语法、特性、使用场景,再配套详解 Stream 流的创建、中间操作、终止操作,全程搭配可直接运行的实战代码,适合零基础入门和查漏补缺。
一、Lambda表达式核心介绍
1.1 什么是 Lambda 表达式?
简单来说:Lambda 就是一段简洁的匿名方法,没有方法名、修饰符、返回值声明,只保留参数和方法体。
如果你使用过 Mybatis-Plus,那么对 Lambda 其实并不陌生——它允许我们用更直观的方式构造查询条件。
1.2 核心前提:函数式接口
函数式接口定义:有且仅有一个抽象方法的接口,可以包含多个默认方法、静态方法。
Java 8 提供了大量内置函数式接口,常用核心接口如下:
接口名 | 抽象方法 | 作用 | 典型使用场景 |
|---|---|---|---|
Consumer<T> | void accept(T t) | 消费型:接收参数,无返回值 | forEach遍历 |
Supplier<T> | T get() | 供给型:无参数,有返回值 | 工厂方法、延迟计算 |
Function<T,R> | R apply(T t) | 函数型:接收 T,返回 R | map转换 |
Predicate<T> | boolean test(T t) | 断言型:接收参数,返回布尔值 | filter过滤 |
1.3 Lambda 标准语法
完整语法格式:
(参数列表) -> {方法体}语法拆解:
(参数列表):对应函数式接口抽象方法的参数,可省略参数类型(编译器自动推断)。如果只有一个参数,且类型可推断,还可省略括号。**
->**:Lambda 运算符,固定写法,连接参数和方法体。**
{方法体}**:具体执行的业务逻辑。如果只有一条语句,可省略{}和return。
1.4 语法简化规则(重点)
Lambda 的核心优势就是简洁,支持多种场景简化:
规则 | 说明 | 示例 |
|---|---|---|
| 参数类型省略 | 编译器可推断类型,无需显式声明 | (String s) -> s.length()→ |
| 单参数省略括号 | 只有一个参数时可省略括号 | (s) -> s.length()→ |
| 无参数保留空括号 | 无参数必须写空括号 | () -> System.out.println("Hi") |
| 单行方法体省略大括号 | 只有一条语句时省略 | s -> {System.out.println(s);}→ |
| 单行 return 省略 | 单行且是 return 语句,可省略 | (a,b)->{return a+b;}→ |
1.5 Lambda 基础示例代码
通过线程创建、集合遍历对比传统写法和 Lambda 写法,直观感受简化效果:
import java.util.ArrayList; import java.util.List; /** * Lambda表达式基础示例 */ publicclass LambdaDemo { public static void main(String[] args) { // 1. 传统匿名内部类创建线程 new Thread(new Runnable() { @Override public void run() { System.out.println("传统方式创建线程"); } }).start(); // 2. Lambda简化线程创建(无参数、单行方法体) new Thread(() -> System.out.println("Lambda方式创建线程")).start(); // 3. 集合遍历对比 List<String> list = new ArrayList<>(); list.add("Java"); list.add("Lambda"); list.add("Stream"); // 传统for-each循环 for (String s : list) { System.out.println("传统遍历:" + s); } // Lambda + forEach遍历(Consumer消费型接口) list.forEach(s -> System.out.println("Lambda遍历:" + s)); // 极致简化:方法引用(后续会扩展) list.forEach(System.out::println); } }二、Stream流核心介绍
2.1 什么是 Stream 流?
Stream 流是 Java 8 基于 Lambda 表达式提供的集合数据处理工具,用于对集合、数组进行高效的筛选、过滤、映射、排序、聚合等操作。
重要理解:
Stream不是数据结构,不存储数据,只是数据的管道流。
通过流水线式操作处理数据源,操作完成后不影响原数据源(不可变性)。
支持并行处理(
parallelStream()),充分利用多核 CPU。
2.2 Stream 操作三大步骤
所有 Stream 代码都遵循固定流程:
创建流:从集合、数组、静态方法获取 Stream 对象。
中间操作:对数据进行处理(筛选、转换、排序等),延迟执行,返回新流,可链式调用。多个中间操作会合并为一个流水线,在终止操作时一次性执行,避免多次迭代。
终止操作:触发流的执行,关闭流,返回结果或无返回值。一个流只能有一个终止操作,执行后流失效。
2.3 Stream 流的创建方式
import java.util.Arrays; import java.util.List; import java.util.stream.Stream; /** * Stream流创建方式 */ publicclass StreamCreateDemo { public static void main(String[] args) { // 1. 集合创建流(最常用) List<String> list = Arrays.asList("A", "B", "C"); Stream<String> stream1 = list.stream(); // 串行流 Stream<String> parallelStream = list.parallelStream(); // 并行流 // 2. 数组创建流 String[] arr = {"Java", "Python", "Go"}; Stream<String> stream2 = Arrays.stream(arr); // 3. Stream静态方法创建空流 Stream<String> stream3 = Stream.empty(); // 4. Stream静态方法创建有限流 Stream<String> stream4 = Stream.of("张三", "李四", "王五"); // 5. 无限流(需配合limit使用) Stream<Double> randoms = Stream.generate(Math::random).limit(5); Stream<Integer> iterated = Stream.iterate(0, n -> n + 2).limit(10); // 0,2,4,... } }三、Stream常用中间操作(链式处理)
中间操作不会立即执行,只有触发终止操作时才会批量执行,支持链式拼接,是 Stream 高效的核心。
3.1filter:过滤数据
基于Predicate断言接口,筛选符合条件的数据。
List<Integer> evens = numbers.stream() .filter(n -> n % 2 == 0) .collect(Collectors.toList());3.2map:数据转换
基于Function函数接口,将元素转换为指定类型(类型转换、字段提取)。
List<String> names = users.stream() .map(User::getName) // 提取姓名 .collect(Collectors.toList()); List<Integer> lengths = words.stream() .map(String::length) // 转换成长度 .collect(Collectors.toList());还有mapToInt、mapToLong、mapToDouble特化操作,避免装箱开销。
3.3sorted:排序
支持自然排序、自定义Comparator排序。
// 自然排序(元素需实现 Comparable) List<Integer> sorted = numbers.stream().sorted().collect(Collectors.toList()); // 自定义倒序 List<Integer> desc = numbers.stream() .sorted((a, b) -> b - a) // 或 Comparator.reverseOrder() .collect(Collectors.toList()); // 按对象属性排序 List<User> ageSorted = users.stream() .sorted(Comparator.comparing(User::getAge)) .collect(Collectors.toList());3.4distinct:去重
基于equals和hashCode方法实现元素去重。
List<Integer> unique = numbers.stream().distinct().collect(Collectors.toList());3.5limit/skip:截取 / 跳过
limit(n):保留前 n 个元素。skip(n):跳过前 n 个元素。
常用于分页或限制无限流。
List<Integer> page = numbers.stream() .skip(10) // 跳过前10条 .limit(5) // 取5条 .collect(Collectors.toList());3.6peek:调试查看
中间操作,用于查看流水线中元素状态(常用于日志调试),不会影响结果。
List<Integer> result = numbers.stream() .filter(n -> n > 5) .peek(System.out::println) // 打印每个通过 filter 的元素 .map(n -> n * 2) .collect(Collectors.toList());3.7flatMap:扁平化映射
当元素本身是集合或数组时,flatMap可将多个子流合并为一个流。
List<List<String>> nested = Arrays.asList( Arrays.asList("a", "b"), Arrays.asList("c", "d") ); List<String> flat = nested.stream() .flatMap(List::stream) // 展平 .collect(Collectors.toList()); // [a,b,c,d]3.8 中间操作综合示例
import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; /** * Stream中间操作实战:过滤、转换、排序、去重、截取 */ publicclass StreamMidOperateDemo { public static void main(String[] args) { List<Integer> numList = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 2, 4, 6); // 需求:筛选偶数、去重、平方、排序、保留前3个 List<Integer> resultList = numList.stream() // 1. 过滤:只保留偶数 .filter(num -> num % 2 == 0) // 2. 去重 .distinct() // 3. 数据转换:求平方 .map(num -> num * num) // 4. 升序排序 .sorted() // 5. 截取前3个元素 .limit(3) // 终止操作:流转集合 .collect(Collectors.toList()); System.out.println("最终结果:" + resultList); // 输出:最终结果:[4, 16, 36] } }四、Stream常用终止操作(触发执行)
终止操作是流的终点,执行后流关闭,无法继续使用。
4.1forEach:遍历元素
遍历流中所有元素,无返回值。常用于副作用操作(如打印、日志)。
numList.stream().forEach(System.out::println);4.2count:统计元素数量
返回流中元素总个数。
long cnt = numList.stream().filter(n -> n > 10).count();4.3max/min:获取最值
根据比较器获取最大值、最小值,返回Optional。
Optional<Integer> max = numList.stream().max(Integer::compareTo); Optional<Integer> min = numList.stream().min(Integer::compareTo); max.ifPresent(System.out::println);4.4collect:收集数据(最核心)
将流数据转换为集合、字符串、Map 等,Collectors工具类提供丰富收集方法。
常用Collectors:
方法 | 作用 |
|---|---|
toList() | 转为 List |
toSet() | 转为 Set |
toMap(keyMapper, valueMapper) | 转为 Map |
joining(delimiter) | 拼接字符串 |
groupingBy(classifier) | 分组 |
partitioningBy(predicate) | 分区(true/false 两组) |
summingInt/ | 统计汇总 |
示例:
// 转 Map (key=姓名, value=年龄) Map<String, Integer> nameAgeMap = users.stream() .collect(Collectors.toMap(User::getName, User::getAge)); // 分组 Map<Integer, List<User>> ageGroup = users.stream() .collect(Collectors.groupingBy(User::getAge)); // 拼接字符串 String names = users.stream().map(User::getName).collect(Collectors.joining(", "));4.5 匹配判断:anyMatch/allMatch/noneMatch
anyMatch:任意一个元素满足条件返回true。allMatch:所有元素满足条件返回true。noneMatch:所有元素都不满足条件返回true。
boolean hasAdult = users.stream().anyMatch(u -> u.getAge() >= 18); boolean allAdult = users.stream().allMatch(u -> u.getAge() >= 18);4.6findFirst/findAny:查找元素
返回Optional,常用于获取第一个或任意一个匹配元素。
Optional<Integer> first = numbers.stream().filter(n -> n > 5).findFirst(); Optional<Integer> any = numbers.parallelStream().filter(n -> n > 5).findAny(); // 并行流中效率更高4.7reduce:归约聚合
将流中元素反复结合,得到一个值。如求和、求积、字符串拼接等。
// 求和(初始值0) int sum = numbers.stream().reduce(0, Integer::sum); // 求最大值(无初始值,返回 Optional) Optional<Integer> maxOpt = numbers.stream().reduce(Integer::max); // 字符串拼接 String concat = words.stream().reduce("", (a, b) -> a + b);五、实战进阶:对象集合Stream操作(工作常用)
实际开发中,Stream 最多用于实体类集合的处理,下面以用户实体为例,演示筛选、字段提取、分组、统计等高频操作。
5.1 实体类
@Data @NoArgsConstructor @AllArgsConstructor public class User { private String name; private Integer age; private String gender; private Double salary; }5.2 集合Stream实战操作
import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.stream.Collectors; /** * 对象集合Stream实战(最常使用场景) */ publicclass StreamUserDemo { public static void main(String[] args) { List<User> userList = Arrays.asList( new User("张三", 20, "男", 5000.0), new User("李四", 25, "女", 6000.0), new User("王五", 30, "男", 7000.0), new User("赵六", 28, "女", 8000.0), new User("孙七", 20, "男", 5500.0) ); // 1. 筛选:年龄大于22的用户 List<User> adultUser = userList.stream() .filter(user -> user.getAge() > 22) .collect(Collectors.toList()); System.out.println("年龄大于22的用户:" + adultUser); // 2. 字段提取:获取所有用户姓名集合 List<String> nameList = userList.stream() .map(User::getName) .collect(Collectors.toList()); System.out.println("所有用户姓名:" + nameList); // 3. 分组:根据性别分组 Map<String, List<User>> genderGroup = userList.stream() .collect(Collectors.groupingBy(User::getGender)); System.out.println("按性别分组:" + genderGroup); // 4. 统计:平均年龄、最大年龄、总薪资 Double avgAge = userList.stream().collect(Collectors.averagingInt(User::getAge)); Integer maxAge = userList.stream().mapToInt(User::getAge).max().orElse(0); Double totalSalary = userList.stream().collect(Collectors.summingDouble(User::getSalary)); System.out.println("用户平均年龄:" + avgAge + ",最大年龄:" + maxAge + ",总薪资:" + totalSalary); // 5. 分组后统计各组平均薪资(分组进阶) Map<String, Double> avgSalaryByGender = userList.stream() .collect(Collectors.groupingBy(User::getGender, Collectors.averagingDouble(User::getSalary))); System.out.println("各性别平均薪资:" + avgSalaryByGender); // 6. 排序+取top3(按薪资降序) List<User> top3Salary = userList.stream() .sorted((u1, u2) -> Double.compare(u2.getSalary(), u1.getSalary())) // 降序 .limit(3) .collect(Collectors.toList()); System.out.println("薪资前三名:" + top3Salary); // 7. 判断是否所有人年龄都大于18 boolean allAdult = userList.stream().allMatch(u -> u.getAge() >= 18); System.out.println("所有人成年:" + allAdult); } }六、Lambda + Stream 核心注意事项(避坑指南)
Stream 流只能使用一次
终止操作执行后,流自动关闭,无法重复调用。若重复使用会抛出IllegalStateException。中间操作延迟执行
所有中间操作不会立即执行,只有在终止操作触发后才会一次性执行。因此,若忘记写终止操作,流不会执行任何处理。不修改原数据源
Stream 所有操作都是基于新流处理,不会改变原集合、数组数据。但若流中元素是可变对象,在操作中修改其内部状态则会影响原集合(谨慎使用)。Lambda 变量捕获限制
Lambda 内部只能访问外层final或隐式 final的变量(即实际未修改的变量),不能修改外层局部变量。但可访问实例变量和静态变量。空指针问题(最常见)
Stream 操作对象集合时,需提前判空,避免空指针异常。可使用Optional或工具类进行防御。性能考量
对于小集合,传统循环可能比 Stream 更快(因 Stream 有创建和开销)。
对于大集合和复杂链式操作,Stream 的延迟执行和内部迭代往往更高效。
使用并行流时注意任务是否适合并行化。
尽量避免副作用
不要在
forEach中修改外部状态(如集合),容易引起并发问题。尽量使用
collect等无副作用的终止操作。
本篇旨在提高代码编写的简洁性和优雅性,如果对你有帮助,建议收藏、分享、推荐,以便需要的时候及时查阅。
附:学习建议
多在代码中尝试链式操作,逐步替代旧有循环。
熟悉
Collectors工具类,它是 Stream 的“瑞士军刀”。结合
Optional使用,避免空指针,进一步提升代码健壮性。
