java中的Lambda表达式使用及说明
一、介绍:Stream的“黄金搭档”
Lambda 表达式是 Java 8 引入的 函数式编程语法糖,核心作用是 简化函数式接口的实现代码(替代冗余的匿名内部类),也是 Stream 流式操作的 “黄金搭档”—— 没有 Lambda,Stream 的链式调用会变得臃肿难读。
二、先搞懂:Lambda 只为 “函数式接口” 服务
Lambda 表达式不能单独使用,它的唯一用途是 快速实现 “函数式接口”:
- 函数式接口:只有 一个抽象方法 的接口(可以有默认方法、静态方法);
- 常见的函数式接口:
Predicate(判断条件)、Function(数据转换)、Consumer(消费数据)、Runnable(无参无返回)等(Stream 操作的参数全是这类接口)。
举个对比:用匿名内部类 vs Lambda 实现 Runnable(线程任务)
// 1. 传统匿名内部类(冗余)
new Thread(new Runnable() {
@Override
public void run() { // 唯一抽象方法
System.out.println("匿名内部类执行线程任务");
}
}).start();
// 2. Lambda 表达式(简洁)
new Thread(() -> System.out.println("Lambda 执行线程任务")).start();Lambda 自动 “对接” 函数式接口的唯一抽象方法,省去了 new 接口()、@Override、方法名等冗余代码。
举个简单的例子:
假设你要给朋友寄一本书(对应 “一段核心逻辑”):
匿名内部类:
就像把书放进一个完整的快递箱 —— 要选箱子(定义匿名类)、填快递单(实现接口方法)、封箱(写方法体),哪怕箱子比书还大(冗余代码比核心逻辑多),也必须按流程来。比如用 Comparator 排序:
// 匿名内部类:“完整快递箱”,冗余代码多
List<Integer> list = Arrays.asList(3,1,2);
Collections.sort(list, new Comparator<Integer>() {
@Override
public int compare(Integer a, Integer b) {
return a - b; // 核心逻辑:“书”
}
});Lambda 表达式:
就像 “裸奔快递”—— 直接把书交给快递员,不用箱子、不用填复杂快递单,只说 “送到 XX 地址”(核心逻辑)。多余的 “包装”(类定义、方法声明)全省略,直奔主题。同样的排序逻辑,Lambda 简化后:
// Lambda:“裸奔快递”,只留核心逻辑 list.sort((a, b) -> a - b);
匿名内部类是 “裹脚布式代码”,Lambda 是 “紧身衣式代码”—— 去掉所有不必要的冗余,只保留核心逻辑,让代码从 “臃肿” 变 “精干”。
传递行为:把 “做事的方法” 当 “快递包裹” 传递(函数式接口的核心)
生活中,你可以把 “做饭” 这个行为(不是做好的饭)告诉别人:比如让妈妈 “按我的方法做饭”(传递行为),而不是自己先做好饭再交给妈妈(传递结果)。
Lambda 就是 “传递行为” 的代码版 —— 它不直接执行逻辑,而是把 “逻辑本身” 当作参数传给方法,让方法按需执行。
举例:用 Stream 过滤列表(传递 “过滤规则” 这个行为):
List<String> fruits = Arrays.asList("苹果", "香蕉", "橙子", "小番茄");
// 传递“长度>2”这个行为给 filter 方法
List<String> longFruits = fruits.stream()
.filter(f -> f.length() > 2) // Lambda:传递“过滤行为”
.collect(Collectors.toList());普通代码是 “传递结果”(比如先过滤好列表再传入方法),Lambda 是 “传递菜谱”(告诉方法 “怎么过滤”,让方法自己执行)—— 灵活度拉满,同一个方法(如 filter)可以接收不同 “行为” 实现不同功能(过滤长度 > 2、过滤包含 “果” 字等)。
方法引用是 “快递的‘加急件’”
如果 Lambda 里的逻辑只是 “直接调用某个已有的方法”(没有额外加工),就像快递是 “直接转寄”(不用拆包、不用额外处理),这时可以用方法引用进一步简化 —— 相当于给快递贴了 “加急标签”,跳过中间冗余的 “Lambda 语法糖”,直接指向目标方法。
- Lambda 写法:就像转寄快递时,你还要跟快递员说 “把这个包裹交给 XX”(多余的 “口头指示”);
- 方法引用写法:直接在包裹上写 “收件人:XX”,快递员直接送达,不用你多废话。
举例:按字符串长度排序:
// Lambda 写法:“口头指示”快递员 list.sort((s1, s2) -> s1.length() - s2.length()); // 方法引用写法:“直接写收件人”,更简洁 list.sort(Comparator.comparingInt(String::length));
Lambda 捕获的变量是 “快递单上的固定信息”—— 一旦下单(定义 Lambda),信息就不能改了,否则快递员(JVM)不知道该按哪个信息执行。
异常处理:不能 “藏着掖着” 未声明的 checked 异常
就像寄快递时不能隐瞒 “易碎品”(否则损坏不赔),Lambda 里如果抛出 checked 异常(如 IOException),必须明确处理(try-catch)或让包含它的方法声明抛出 —— 不能偷偷抛出,否则编译器会 “拒收”。
// 错误:未处理 checked 异常
List<File> files = Arrays.asList(new File("a.txt"), new File("b.txt"));
files.stream().map(file -> file.getCanonicalPath()); // getCanonicalPath 抛出 IOException
// 正确:要么 try-catch,要么声明异常
files.stream().map(file -> {
try {
return file.getCanonicalPath();
} catch (IOException e) {
throw new RuntimeException(e); // 转为 unchecked 异常
}
});Lambda 里的 checked 异常是 “快递里的易碎品”—— 必须提前告诉 “快递员”(编译器)怎么处理(try-catch),否则 “快递”(代码)无法正常投递(编译报错)。
三、Lambda 核心语法:(参数) -> 代码块
语法格式:(参数列表) -> { 业务代码 },有 3 个关键部分:
| 部分 | 说明 |
|---|---|
| (参数列表) | 对应函数式接口抽象方法的参数(可省略参数类型,单参数可省略括号) |
| -> | 箭头运算符,分隔参数和代码块(固定写法) |
| { 代码块 } | 对应抽象方法的实现逻辑(单条语句可省略大括号,有返回值时可省略 return) |
语法简化规则(越用越简洁)
- 省略参数类型:Java 能通过接口自动推断参数类型;
- 单参数省略括号:只有 1 个参数时,
(a)可简化为a; - 单语句省略大括号:代码块只有 1 行时,
{ ... }可省略; - 单返回语句省略 return:若代码块是返回语句,省略大括号的同时可省略
return。
语法示例(以Comparator接口为例,用于排序)
// 原始 Lambda(无简化)
Comparator<Integer> comp1 = (Integer a, Integer b) -> {
return b - a; // 降序排序
};
// 简化1:省略参数类型(Java 自动推断)
Comparator<Integer> comp2 = (a, b) -> {
return b - a;
};
// 简化2:单返回语句省略大括号和 return
Comparator<Integer> comp3 = (a, b) -> b - a;
// 最终简化结果(Stream 中直接使用)
List<Integer> list = Arrays.asList(3,1,2);
list.stream().sorted((a, b) -> b - a).forEach(System.out::println); // 输出 3,2,1四、Lambda 与 Stream 的 “天作之合”
Stream 的中间操作(filter、map 等)和终端操作(forEach、sorted 等),参数全是函数式接口 ——Lambda 刚好能简化这些接口的实现,让 Stream 链式调用更流畅。
Stream 常见操作 | 函数式接口 | 接口抽象方法 | Lambda 示例(实现逻辑) |
|---|---|---|---|
| filter() | Predicate<T> | boolean test(T t) | emp -> emp.getAge() >= 30(筛选 30 岁以上员工) |
| map() | Function<T,R> | R apply(T t) | emp -> emp.getSalary()(提取员工工资) |
| forEach() | Consumer<T> | void accept(T t) | System.out::println(打印元素,方法引用简化) |
| sorted() | Comparator<T> | int compare(T a,T b) | (a,b) -> b - a(降序排序) |
| findAny() | Predicate<T> | boolean test(T t) | n -> n % 2 == 0(找任意偶数) |
示例:
List<Employee> empList = Arrays.asList(
new Employee("张三", 28, 15000),
new Employee("李四", 32, 18000),
new Employee("赵六", 35, 20000)
);
// 需求:筛选30岁以上员工 → 提取姓名 → 按姓名长度降序 → 打印
empList.stream()
.filter(emp -> emp.getAge() >= 30) // Predicate:筛选条件
.map(Employee::getName) // Function:提取姓名(方法引用简化Lambda)
.sorted((name1, name2) -> name2.length() - name1.length()) // Comparator:排序
.forEach(System.out::println); // Consumer:打印
// 输出:赵六(2字)、李四(2字)(若有3字姓名会排在前面)五、Lambda 简化技巧:方法引用(::)
当 Lambda 的代码块只是 调用一个已存在的方法(无额外逻辑),可以用 类名::方法名 或 对象::方法名 进一步简化,这就是 方法引用。
| 方法引用类型 | 语法格式 | Lambda 等效写法 | 示例(Stream 中) |
|---|---|---|---|
| 静态方法引用 | 类名::静态方法 | (a,b) -> 类名.静态方法(a,b) | Integer::compare → (a,b) -> Integer.compare(a,b) |
| 实例方法引用(对象) | 对象::实例方法 | (x) -> 对象.实例方法(x) | System.out::println → (s) -> System.out.println(s) |
| 实例方法引用(参数) | 类名::实例方法 | (a,b) -> a.实例方法(b) | String::equals → (s1,s2) -> s1.equals(s2) |
| 构造器引用 | 类名::new | (参数) -> new 类名(参数) | ArrayList::new → () -> new ArrayList<>() |
示例:
// 1. 实例方法引用(对象):System.out::println 替代 (s) -> System.out.println(s)
List<String> names = Arrays.asList("张三", "李四");
names.stream().forEach(System.out::println);
// 2. 实例方法引用(参数):String::length 替代 (s) -> s.length()
List<Integer> nameLengths = names.stream()
.map(String::length) // 提取姓名长度
.collect(Collectors.toList());
// 3. 构造器引用:ArrayList::new 替代 () -> new ArrayList<>()
List<String> newNames = names.stream()
.filter(name -> name.length() == 2)
.collect(Collectors.toCollection(ArrayList::new));六、Lambda 中的变量捕获
Lambda 可以访问外部变量,但有严格限制:
- 只能访问 final 或 effectively final 的变量(即变量声明后不再修改);
- 不能修改外部变量(否则编译报错)。
示例:
// 正确:外部变量是 effectively final(声明后未修改)
int minAge = 30;
empList.stream().filter(emp -> emp.getAge() >= minAge).forEach(System.out::println);
// 错误:修改外部变量(编译报错)
int count = 0;
empList.stream().forEach(emp -> {
count++; // 报错:Variable used in lambda expression should be final or effectively final
});原因:Lambda 本质是匿名函数,可能在多线程中执行,禁止修改外部变量是为了避免线程安全问题。
七、Lambda 常见误区
- 误以为能替代所有匿名内部类:只有 “函数式接口”(单抽象方法)才能用 Lambda,多抽象方法的接口不行;
- 修改外部变量:违反 “final 或 effectively final” 规则,编译报错;
- 过度简化导致可读性下降:复杂逻辑(多行业务代码)不适合用 Lambda,建议拆分为普通方法,再用方法引用调用;
- 忽略异常处理:Lambda 中抛出受检异常(如
IOException),需在代码块中捕获,或函数式接口的抽象方法声明该异常。
示例:
// 错误:Lambda 中抛出受检异常,未处理
List<String> files = Arrays.asList("a.txt", "b.txt");
files.stream().map(file -> new FileInputStream(file)); // 编译报错:未处理 IOException
// 正确:捕获异常
files.stream().map(file -> {
try {
return new FileInputStream(file);
} catch (IOException e) {
throw new RuntimeException(e); // 转为运行时异常抛出
}
});八、总结
Lambda 表达式的核心价值是 “简化代码 + 传递行为”:
- 专为函数式接口设计,替代冗余的匿名内部类;
- 语法简洁灵活,配合 Stream 能大幅提升代码可读性和开发效率;
- 方法引用是 Lambda 的 “进阶简化技巧”,适合简单的方法调用场景;
- 注意变量捕获规则和异常处理,避免踩坑。
一句话记住 Lambda:“少写代码,多传逻辑”—— 它让 Java 从 “面向对象” 向 “函数式编程” 迈出了重要一步,而 Stream 则是 Lambda 最经典的应用场景。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
相关文章
深入浅析java web log4j 配置及在web项目中配置Log4j的技巧
这篇文章主要介绍了2015-11-11
SpringBoot+JPA 分页查询指定列并返回指定实体方式
这篇文章主要介绍了SpringBoot+JPA 分页查询指定列并返回指定实体方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教2021-12-12
Spring Boot中使用Swagger3.0.0版本构建RESTful APIs的方法
Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务,这篇文章主要介绍了Spring Boot中使用Swagger3.0.0版本构建RESTful APIs的方法,需要的朋友可以参考下2022-11-11
Java Web过滤器的核心原理、实现与执行顺序配置方法(最新整理)
本文介绍了JavaWeb过滤器的核心概念、实现方式及执行顺序,过滤器允许在请求到达Servlet之前或响应返回客户端之前对请求和响应进行处理,感兴趣的朋友跟随小编一起看看吧2025-12-12
SWT(JFace) Wizard(Eclipse插件编程必备)
SWT(JFace)小制作:Wizard(Eclipse插件编程必备)2009-06-06


最新评论