JavaLambda表达式详解
Java Lambda 表达式详解
本章位置:第二阶段 Java 核心框架
前置知识:接口、匿名内部类、集合框架
下一篇:Stream 流
学习目标:掌握函数式接口、Lambda 语法、常用函数式接口、方法引用、变量捕获,以及 Lambda 在线程、集合排序和 Stream 中的应用。
一、Lambda 是什么
Lambda 表达式是 Java 8 引入的重要语法,主要用于:
简化函数式接口的实现
匿名内部类:
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println("任务执行");
}
};
Lambda:
Runnable task =
() ->
System.out.println(
"任务执行"
);
Lambda 不只是“少写代码”,更重要的是:
可以把一段行为作为参数传递给方法。
二、什么是函数式接口
函数式接口:
只有一个抽象方法的接口。
例如:
@FunctionalInterface
public interface Calculator {
int calculate(
int a,
int b
);
}
可以使用:
Calculator add =
(
a,
b
) ->
a + b;
调用:
int result =
add.calculate(
10,
20
);
结果:
30
三、@FunctionalInterface
@FunctionalInterface
明确告诉编译器:
这个接口应该是函数式接口
如果增加第二个抽象方法:
void test();
编译器会报错。
注意:
default 方法
static 方法
不会破坏函数式接口。
四、Lambda 基本语法
基本形式:
(参数列表) -> { 方法体 }
无参数:
() -> {
System.out.println(
"Hello"
);
}
一个参数:
name -> {
System.out.println(
name
);
}
多个参数:
(
a,
b
) -> {
return a + b;
}
五、Lambda 简写规则
完整:
(
int a,
int b
) -> {
return a + b;
}
参数类型可以省略:
(
a,
b
) -> {
return a + b;
}
只有一条返回表达式:
(
a,
b
) ->
a + b
只有一个参数:
name ->
System.out.println(
name
)
可以省略参数括号。
六、Lambda 和匿名内部类
匿名内部类:
Comparator<Student> comparator =
new Comparator<Student>() {
@Override
public int compare(
Student a,
Student b
) {
return Integer.compare(
a.getAge(),
b.getAge()
);
}
};
Lambda:
Comparator<Student> comparator =
(
a,
b
) ->
Integer.compare(
a.getAge(),
b.getAge()
);
七、Lambda 不能替代所有匿名内部类
只有:
函数式接口
才能使用 Lambda。
如果接口:
public interface Handler {
void start();
void stop();
}
有两个抽象方法,就不能:
Handler handler =
() -> {};
Lambda 也不能直接:
继承普通类
继承抽象类
而匿名内部类可以。
八、Java 常用函数式接口
主要位于:
java.util.function
重点掌握:
Supplier<T>
Consumer<T>
Function<T,R>
Predicate<T>
九、Supplier
特点:
无参数
有返回值
核心方法:
T get();
例如:
Supplier<String> supplier =
() ->
"Hello";
String value =
supplier.get();
典型场景:
生产数据
延迟创建对象
提供默认值
十、Consumer
特点:
一个参数
无返回值
核心:
void accept(
T t
);
例如:
Consumer<String> consumer =
name ->
System.out.println(
name
);
consumer.accept(
"张三"
);
典型:
打印
保存
处理
发送
十一、Function<T,R>
特点:
一个参数
一个返回值
核心:
R apply(
T t
);
例如:
Function<String, Integer>
function =
text ->
text.length();
Integer length =
function.apply(
"Java"
);
典型:
数据映射
类型转换
对象转换
十二、Predicate
特点:
一个参数
返回 boolean
核心:
boolean test(
T t
);
例如:
Predicate<Integer> adult =
age ->
age >= 18;
使用:
adult.test(
20
);
典型:
过滤
规则判断
参数校验
十三、其他常见函数式接口
BiFunction<T,U,R>
两个参数,一个返回值
BiConsumer<T,U>
两个参数,无返回值
BiPredicate<T,U>
两个参数,返回 boolean
UnaryOperator<T>
T -> T
BinaryOperator<T>
(T,T) -> T
例如:
BinaryOperator<Integer> add =
(
a,
b
) ->
a + b;
十四、基本类型专用接口
为了减少装箱拆箱,还有:
IntSupplier
IntConsumer
IntPredicate
IntFunction
Long...
Double...
例如:
IntPredicate positive =
value ->
value > 0;
十五、Lambda 与 Comparator
按年龄升序:
students.sort(
(
a,
b
) ->
Integer.compare(
a.getAge(),
b.getAge()
)
);
更推荐:
students.sort(
Comparator.comparingInt(
Student::getAge
)
);
十六、reversed()
降序:
students.sort(
Comparator
.comparingInt(
Student::getAge
)
.reversed()
);
十七、thenComparing()
多字段排序:
students.sort(
Comparator
.comparingInt(
Student::getScore
)
.reversed()
.thenComparing(
Student::getName
)
);
十八、为什么不推荐直接 a - b
例如:
a - b
在极端整数情况下可能:
溢出
更推荐:
Integer.compare(
a,
b
);
十九、Lambda 与线程
匿名内部类:
new Thread(
new Runnable() {
@Override
public void run() {
System.out.println(
"线程执行"
);
}
}
).start();
Lambda:
new Thread(
() ->
System.out.println(
"线程执行"
)
).start();
二十、Lambda 与集合
List<String> names =
List.of(
"张三",
"李四",
"王五"
);
names.forEach(
name ->
System.out.println(
name
)
);
二十一、方法引用
如果 Lambda 只是调用一个已有方法,可以用:
::
简化。
Lambda:
name ->
System.out.println(
name
)
方法引用:
System.out::println
所以:
names.forEach(
System.out::println
);
二十二、方法引用四种常见形式
类名::静态方法
对象::实例方法
类名::实例方法
类名::new
二十三、静态方法引用
Lambda:
text ->
Integer.parseInt(
text
)
方法引用:
Integer::parseInt
二十四、对象实例方法引用
System.out::println
等价:
value ->
System.out.println(
value
)
二十五、类名::实例方法
String::length
等价:
text ->
text.length()
例如:
Function<String, Integer>
length =
String::length;
二十六、Student::getName
Function<Student, String>
getName =
Student::getName;
等价:
student ->
student.getName()
二十七、构造方法引用
无参:
Supplier<Student> supplier =
Student::new;
等价:
() ->
new Student()
二十八、有参构造引用
假设:
public Student(
String name
) {
}
可以:
Function<String, Student>
creator =
Student::new;
二十九、方法引用不是必须使用
如果:
Student::getName
不容易理解,可以保留:
student ->
student.getName()
原则:
可读性优先
三十、变量捕获
Lambda 可以使用外部局部变量:
String prefix =
"Hello";
Consumer<String> consumer =
name ->
System.out.println(
prefix
+ " "
+ name
);
三十一、effectively final
被 Lambda 捕获的局部变量必须:
final
或实际上不再修改
例如:
String prefix =
"Hello";
后面如果:
prefix =
"Hi";
Lambda 又引用 prefix,
编译会失败。
这种未显式写 final、但实际上没有修改的变量叫:
effectively final
有效 final
三十二、成员变量可以修改
例如:
public class Counter {
private int count;
public void test() {
Runnable task =
() ->
count++;
}
}
成员变量:
不受 effectively final 限制
但如果多线程同时修改:
要考虑线程安全
三十三、Lambda 中的 this
Lambda 中:
this
通常指:
外部类当前对象
Lambda 没有自己的独立 this。
三十四、匿名内部类中的 this
匿名内部类中:
this
通常代表:
匿名内部类对象本身
所以 Lambda 和匿名内部类:
this 语义不同
三十五、Predicate 组合
Predicate 提供:
and
or
negate
例如:
Predicate<Integer> adult =
age ->
age >= 18;
Predicate<Integer> under60 =
age ->
age < 60;
Predicate<Integer> workingAge =
adult.and(
under60
);
三十六、Predicate.negate()
Predicate<Integer> notAdult =
adult.negate();
三十七、Function.andThen()
Function<Integer, Integer>
doubleValue =
value ->
value * 2;
Function<Integer, String>
toText =
value ->
"结果:"
+ value;
Function<Integer, String>
combined =
doubleValue
.andThen(
toText
);
调用顺序:
先 doubleValue
再 toText
三十八、Function.compose()
f.compose(g)
表示:
先 g
再 f
与:
andThen
顺序相反。
三十九、Consumer.andThen()
Consumer<String> print =
System.out::println;
Consumer<String> save =
value ->
System.out.println(
"保存:"
+ value
);
Consumer<String> both =
print.andThen(
save
);
四十、集合 removeIf()
students.removeIf(
student ->
student.getScore()
< 60
);
这里接收:
Predicate
四十一、List.replaceAll()
names.replaceAll(
String::toUpperCase
);
这里接收:
UnaryOperator
四十二、Map.forEach()
map.forEach(
(
key,
value
) ->
System.out.println(
key
+ "="
+ value
)
);
接收:
BiConsumer
四十三、computeIfAbsent()
Map<String, List<String>>
map =
new HashMap<>();
map.computeIfAbsent(
"Java",
key ->
new ArrayList<>()
).add(
"Lambda"
);
四十四、Lambda 与 Optional
optional.ifPresent(
value ->
System.out.println(
value
)
);
可以:
optional.ifPresent(
System.out::println
);
四十五、Lambda 与 Stream
后面 Stream 的:
filter
map
sorted
forEach
reduce
都会大量接收函数式接口。
例如:
students.stream()
.filter(
student ->
student.getScore()
>= 60
)
.map(
Student::getName
)
.forEach(
System.out::println
);
四十六、filter() 需要什么
filter(...)
需要:
Predicate<T>
因为它要判断:
这个元素保不保留
四十七、map() 需要什么
map(...)
需要:
Function<T,R>
因为它要:
把一个元素转换成另一个元素
四十八、forEach() 需要什么
forEach(...)
需要:
Consumer<T>
因为它:
接收元素
执行操作
不需要返回值
四十九、把行为作为参数
这是 Lambda 真正重要的思想。
例如:
public static List<Student>
filterStudents(
List<Student> students,
Predicate<Student> condition
) {
List<Student> result =
new ArrayList<>();
for (
Student student :
students
) {
if (
condition.test(
student
)
) {
result.add(
student
);
}
}
return result;
}
五十、传入不同规则
筛选及格:
filterStudents(
students,
student ->
student.getScore()
>= 60
);
筛选成年人:
filterStudents(
students,
student ->
student.getAge()
>= 18
);
筛选软件工程:
filterStudents(
students,
student ->
"软件工程".equals(
student.getMajor()
)
);
同一个方法:
可以复用不同规则
五十一、函数式编程思想
传统:
方法内部写死规则
函数式思路:
把规则作为行为参数传入
让程序更:
灵活
可组合
可复用
五十二、Lambda 与策略模式
传统策略模式需要:
Strategy 接口
多个实现类
传入不同策略对象
如果策略接口是函数式接口,
简单策略可以直接:
value ->
...
减少大量一次性实现类。
五十三、Lambda 与回调
例如:
public static void execute(
Runnable callback
) {
System.out.println(
"开始执行"
);
callback.run();
}
调用:
execute(
() ->
System.out.println(
"执行结束"
)
);
这就是:
回调
五十四、Lambda 与异步编程
例如:
CompletableFuture
.supplyAsync(
() ->
"查询结果"
)
.thenAccept(
System.out::println
);
Lambda 非常适合:
异步任务
回调链
五十五、Lambda 与受检异常
标准 Function:
R apply(T t)
没有:
throws IOException
所以不能直接写:
path ->
Files.readString(
path
)
如果会抛受检异常,需要处理。
例如:
Function<Path, String> reader =
path -> {
try {
return Files.readString(
path
);
} catch (
IOException e
) {
throw new UncheckedIOException(
e
);
}
};
五十六、Lambda 和重载歧义
如果有:
void run(
Runnable task
)
void run(
Callable<String> task
)
有些 Lambda 可能让编译器难以确定:
到底匹配哪个重载
可以显式声明:
Callable<String> task =
() ->
"hello";
run(task);
五十七、Lambda 类型推断
例如:
Comparator<Student> comparator =
(
a,
b
) ->
Integer.compare(
a.getAge(),
b.getAge()
);
虽然没写:
Student a
Student b
编译器能从:
Comparator<Student>
推断。
五十八、常见错误:不是函数式接口
错误:
interface Demo {
void a();
void b();
}
不能:
Demo demo =
() -> {};
五十九、常见错误:多个参数不加括号
错误:
a, b ->
a + b
正确:
(
a,
b
) ->
a + b
六十、常见错误:有大括号但忘 return
错误:
(
a,
b
) -> {
a + b;
}
正确:
(
a,
b
) -> {
return a + b;
}
或者:
(
a,
b
) ->
a + b
六十一、常见错误:修改被捕获局部变量
int count = 0;
Runnable task =
() ->
System.out.println(
count
);
count++;
编译失败。
因为 count 不再:
effectively final
六十二、常见错误:复杂代码全部塞进 Lambda
不推荐:
list.forEach(
item -> {
// 很长的业务逻辑
}
);
更推荐:
list.forEach(
this::processItem
);
复杂逻辑抽成:
具名方法
六十三、常见错误:为了短而牺牲可读性
Lambda 可以很短,
但:
越短不代表越好
核心标准:
是否容易理解
六十四、练习题 1:Runnable
分别用:
匿名内部类
Lambda
创建线程任务。
六十五、练习题 2:Calculator
定义:
@FunctionalInterface
interface Calculator {
int calculate(
int a,
int b
);
}
使用 Lambda 实现:
加
减
乘
除
六十六、练习题 3:Predicate
判断 Student:
是否及格
六十七、练习题 4:Function
把:
Student
转换成:
String
返回姓名。
六十八、练习题 5:Consumer
打印:
学生姓名 + 成绩
六十九、练习题 6:Supplier
使用:
Student::new
创建 Student。
七十、练习题 7:Comparator
分别:
年龄升序
成绩降序
排序。
七十一、练习题 8:多字段排序
要求:
成绩降序
成绩相同时
姓名升序
使用:
Comparator
.comparing...
.thenComparing(...)
七十二、练习题 9:方法引用
把:
name ->
System.out.println(
name
)
改为:
System.out::println
七十三、练习题 10:自定义过滤器
实现:
filterStudents(
List<Student>,
Predicate<Student>
)
然后传入至少:
三个不同过滤规则
七十四、必须掌握的四个核心函数式接口
Supplier<T>
无参 → 有返回值
Consumer<T>
有参数 → 无返回值
Function<T,R>
有参数 → 有返回值
Predicate<T>
有参数 → boolean
七十五、方法引用必须掌握
类名::静态方法
对象::实例方法
类名::实例方法
类名::new
七十六、必须回答的问题
学完后应该能够回答:
1. Lambda 是什么?
2. 什么是函数式接口?
3. @FunctionalInterface 有什么作用?
4. Lambda 和匿名内部类有什么区别?
5. Lambda 可以替代所有匿名内部类吗?
6. Supplier / Consumer / Function / Predicate 分别是什么?
7. 什么是方法引用?
8. Student::getName 属于什么方法引用?
9. 什么是 effectively final?
10. Lambda 中 this 指向谁?
11. 为什么 Stream 大量使用 Lambda?
12. Lambda 的核心价值是什么?
七十七、Lambda 知识结构
Lambda
│
├─ 函数式接口
│ ├─ Supplier
│ ├─ Consumer
│ ├─ Function
│ └─ Predicate
│
├─ 语法
│ ├─ 无参
│ ├─ 单参
│ ├─ 多参
│ └─ 返回值
│
├─ 方法引用
│ ├─ 类::静态方法
│ ├─ 对象::实例方法
│ ├─ 类::实例方法
│ └─ 类::new
│
├─ 变量捕获
│ └─ effectively final
│
└─ 应用
├─ Comparator
├─ Thread
├─ Collection
├─ Callback
└─ Stream
七十八、从 Lambda 到 Stream
现在我们已经会写:
Predicate<Student> condition =
student ->
student.getScore()
>= 60;
也会:
Function<Student, String> mapper =
Student::getName;
下一篇 Stream 会把这些行为组合起来:
students.stream()
.filter(
student ->
student.getScore()
>= 60
)
.map(
Student::getName
)
.forEach(
System.out::println
);
七十九、本章总结
Lambda 最核心的一句话:
Lambda 是函数式接口的一种简洁实现方式。
它的核心价值不只是:
减少匿名内部类代码
而是:
允许把行为作为参数传递
学习顺序:
函数式接口
↓
Lambda 基础语法
↓
Supplier / Consumer / Function / Predicate
↓
Comparator
↓
方法引用
↓
变量捕获
↓
集合与线程应用
↓
Stream
掌握 Lambda 以后,下一篇正式进入:
Java Stream 流详解
也就是 Java 8 以后集合处理非常重要的函数式编程能力。