Lambda 表达式:Java 8+ 的 "简洁函数式语法"
Lambda 表达式是 Java 8 引入的 核心函数式编程特性 ,本质是「匿名函数」------ 没有类名、方法名的简洁函数定义,可直接作为参数传递或赋值给变量,核心目的是 简化代码、减少冗余(如匿名内部类的模板代码),同时让代码更聚焦 "逻辑本身"。
一、为什么需要 Lambda 表达式?(对比匿名内部类)
在 Java 8 之前,若要使用「函数式接口」(只有一个抽象方法的接口),需通过匿名内部类实现,代码冗余且可读性差。Lambda 表达式可直接替代这一场景,大幅简化代码。
示例:线程创建(匿名内部类 vs Lambda)
1. 匿名内部类(Java 8 前)
java
// 实现 Runnable 接口(函数式接口,仅 run() 一个抽象方法)
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("匿名内部类实现线程");
}
}).start();
- 问题:模板代码多(
new Runnable()、@Override、方法体框架),核心逻辑(System.out.println(...))被淹没。
2. Lambda 表达式(Java 8+)
java
// Lambda 直接替代匿名内部类,只保留核心逻辑
new Thread(() -> System.out.println("Lambda 实现线程")).start();
- 优势:去掉所有模板代码,直接聚焦 "线程要执行的逻辑",代码简洁直观。
二、Lambda 表达式的核心语法
Lambda 表达式的语法格式可概括为:(参数列表) -> { 方法体 }
1. 语法拆解
| 部分 | 说明 |
|---|---|
(参数列表) |
对应函数式接口中抽象方法的参数:- 无参数时写 ();- 单参数可省略括号(如 s -> ...);- 多参数用逗号分隔(如 (a, b) -> ...);- 可省略参数类型(JVM 自动推断,如 (a, b) 等价于 (int a, int b))。 |
-> |
Lambda 运算符(箭头符号),分隔参数列表和方法体,意为 "将参数传递给逻辑"。 |
{ 方法体 } |
对应抽象方法的实现逻辑:- 单条语句可省略大括号和 ;(如 () -> System.out.println(...));- 多条语句必须用大括号包裹,且需显式写 return(若有返回值)。 |
2. 常见语法变体(实战常用)
以「函数式接口 Comparator<Integer>」(排序逻辑)为例,展示不同语法写法:
java
import java.util.Arrays;
import java.util.Comparator;
public class LambdaSyntax {
public static void main(String[] args) {
Integer[] arr = {3, 1, 2};
// 1. 完整语法(多参数、显式类型、多条语句)
Comparator<Integer> comp1 = (Integer a, Integer b) -> {
System.out.println("比较 a=" + a + " 和 b=" + b);
return a - b; // 有返回值需显式 return
};
Arrays.sort(arr, comp1);
// 2. 简化1:省略参数类型(JVM 自动推断)
Comparator<Integer> comp2 = (a, b) -> {
return a - b;
};
// 3. 简化2:单条返回语句,省略大括号和 return
Comparator<Integer> comp3 = (a, b) -> a - b;
// 4. 最终简化(直接作为参数传递)
Arrays.sort(arr, (a, b) -> a - b); // 最简洁写法
}
}
三、Lambda 表达式的使用前提:函数式接口
Lambda 表达式 不能单独存在 ,必须绑定到一个「函数式接口」(Functional Interface)------ 即 只有一个抽象方法的接口(可含默认方法、静态方法)。
1. 核心特点
- 接口上可加
@FunctionalInterface注解(可选,但推荐,编译器会强制检查是否符合函数式接口规范); - 抽象方法的签名(参数类型、返回值类型)必须与 Lambda 表达式的语法匹配("函数签名一致")。
2. Java 内置的常用函数式接口(java.util.function 包)
Java 8 提供了大量现成的函数式接口,覆盖大部分开发场景,无需自定义:
| 接口名 | 抽象方法 | 功能描述 | 示例场景 |
|---|---|---|---|
Runnable |
void run() |
无参数、无返回值 | 线程任务、异步回调 |
Consumer<T> |
void accept(T t) |
接收一个参数 T,无返回值 | 遍历集合(如 forEach) |
Supplier<T> |
T get() |
无参数,返回一个 T 类型值 | 生成对象、延迟加载 |
Function<T, R> |
R apply(T t) |
接收 T 类型参数,返回 R 类型 | 数据转换(如 String→Integer) |
Predicate<T> |
boolean test(T t) |
接收 T 类型参数,返回布尔值 | 条件过滤(如集合筛选) |
BiFunction<T,U,R> |
R apply(T t, U u) |
接收 T、U 两个参数,返回 R | 多参数转换(如 a+b→字符串) |
3. 示例:使用内置函数式接口
java
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.Function;
public class BuiltInFunctionalInterface {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("Java");
list.add("Lambda");
list.add("Stream");
// 1. Consumer<T>:遍历集合(无返回值,消费参数)
list.forEach(s -> System.out.println(s)); // 输出所有元素
// 2. Predicate<T>:筛选条件(返回布尔值)
List<String> longStrList = filter(list, s -> s.length() > 4);
System.out.println(longStrList); // 输出 [Lambda, Stream]
// 3. Function<T, R>:数据转换(String→Integer)
List<Integer> strLengthList = map(list, s -> s.length());
System.out.println(strLengthList); // 输出 [4, 6, 6]
}
// 筛选集合:按 predicate 条件保留元素
private static <T> List<T> filter(List<T> list, Predicate<T> predicate) {
List<T> result = new ArrayList<>();
for (T t : list) {
if (predicate.test(t)) {
result.add(t);
}
}
return result;
}
// 转换集合:按 function 将 T 转为 R
private static <T, R> List<R> map(List<T> list, Function<T, R> function) {
List<R> result = new ArrayList<>();
for (T t : list) {
result.add(function.apply(t));
}
return result;
}
}
四、Lambda 表达式的核心特性
1. 闭包特性:访问外部变量
Lambda 表达式可访问外部的局部变量或成员变量,但有约束:
- 局部变量:必须是「最终变量(final)」或「effectively final(实际不可变,即声明后未重新赋值)」;
- 成员变量:无约束(可修改)。
示例:
java
public class LambdaClosure {
private static String prefix = "Hello: "; // 成员变量(可修改)
public static void main(String[] args) {
String suffix = "!"; // 局部变量(effectively final,未重新赋值)
// final String suffix = "!"; // 显式 final 也可以
Consumer<String> consumer = s -> {
prefix = "Hi: "; // 允许修改成员变量
System.out.println(prefix + s + suffix); // 访问局部变量 suffix
};
consumer.accept("Lambda"); // 输出 Hi: Lambda!
}
}
2. 类型推断
Lambda 表达式的参数类型、返回值类型无需显式声明,JVM 会根据「函数式接口的抽象方法签名」自动推断,简化代码。
示例:
java
// Function<String, Integer> 接口,抽象方法是 Integer apply(String t)
Function<String, Integer> strToInt = s -> s.length();
// JVM 推断:s 是 String 类型,返回值是 Integer 类型(无需显式写 (String s) -> (int) s.length())
3. 与匿名内部类的区别
| 对比维度 | Lambda 表达式 | 匿名内部类 |
|---|---|---|
| 适用场景 | 仅函数式接口(单抽象方法) | 任意接口、抽象类 |
| 语法简洁度 | 极高(无模板代码) | 冗余(需写类体、@Override) |
this 指向 |
指向外部类的对象 | 指向匿名内部类自身实例 |
| 性能 | 更优(无额外类对象创建) | 略差(创建匿名类实例) |
五、Lambda 表达式的实战场景
Lambda 表达式最常用的场景是 配合 Stream API 进行集合操作(过滤、排序、转换、聚合),也可用于线程、回调函数等场景。
示例:Stream + Lambda 简化集合操作
java
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class LambdaStream {
public static void main(String[] args) {
List<Person> people = Arrays.asList(
new Person("张三", 20, "男"),
new Person("李四", 25, "男"),
new Person("王五", 18, "女"),
new Person("赵六", 30, "男")
);
// 需求:筛选 20 岁以上的男性 → 提取姓名 → 按年龄升序排序 → 转为列表
List<String> result = people.stream()
.filter(p -> p.getAge() > 20 && "男".equals(p.getGender())) // 过滤(Predicate)
.sorted((p1, p2) -> p1.getAge() - p2.getAge()) // 排序(Comparator)
.map(Person::getName) // 转换(Function,方法引用简化 Lambda)
.collect(Collectors.toList()); // 聚合为列表
System.out.println(result); // 输出 [李四, 赵六]
}
static class Person {
private String name;
private int age;
private String gender;
// 构造方法、getter 省略
public Person(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
public String getName() { return name; }
public int getAge() { return age; }
public String getGender() { return gender; }
}
}
总结
Lambda 表达式的核心价值是「简洁 + 聚焦逻辑」,关键要点:
- 本质是匿名函数,语法为
(参数) -> 方法体; - 必须绑定到函数式接口(单抽象方法接口);
- 常用 Java 内置函数式接口(
Consumer/Predicate/Function等); - 常与 Stream API 配合,大幅简化集合操作;
- 支持闭包特性,可访问外部变量(局部变量需不可变)。
掌握 Lambda 表达式后,能显著提升代码简洁度和开发效率,是 Java 8+ 开发的必备技能。