背景
本文是《Java 后端从小白到大神》修仙系列第十三篇
,正式进入Java后端
世界,本篇文章主要聊Java基础
。若想详细学习请点击首篇博文,我们开始把。
文章概览
- Lambda表达式
1. Lambda 表达式定义
Lambda 表达式是 Java 8 引入的一种 匿名函数,用于简化函数式接口
(只有一个抽象方法的接口)的实现。
- 核心作用:替代冗余的
匿名内部类
,使代码更简洁。
- 核心思想:将函数作为参数传递,支持函数式编程。
2. 语法格式
- 参数列表:可以是空参数、单参数或多参数,类型可省略(编译器自动推断)。
- 箭头符号:
->
分隔参数和代码体。
- 代码体:单行代码可省略
{}
和 return
,多行需明确写 {}
和 return
。
语法示例:
场景 |
Lambda 写法 |
等效匿名内部类写法 |
无参数,无返回值 |
() -> System.out.println("Hello") |
new Runnable() { run() { ... }} |
单参数,无类型 |
s -> System.out.println(s) |
|
多参数,带类型 |
(int a, int b) -> a + b |
|
多行代码体 |
(a, b) -> { int c = a + b; return c; } |
|
3. 使用技巧
1. 简化代码
对比传统匿名内部类:
1
2
3
4
5
6
7
8
9
10
|
// 传统写法
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("Running");
}
};
// Lambda 写法
Runnable r = () -> System.out.println("Running");
|
2. 方法引用(Method Reference)
进一步简化 Lambda,使用 ::
符号:
1
2
|
List<String> list = Arrays.asList("A", "B", "C");
list.forEach(System.out::println); // 等效于 s -> System.out.println(s)
|
3. 变量捕获
Lambda 可以访问外部的 final
或等效 final
的局部变量。
1. 为什么需要有效 final:
- 线程安全:如果多个线程同时执行同一个 Lambda 表达式,并且该表达式访问了外部变量,那么这些变量的状态必须是确定的,以避免竞争条件。
- 编译器优化:编译器可以在知道变量不会改变的情况下进行某些优化,提高性能。
- 代码可读性和维护性:限制变量的可变性使得代码更易于理解和维护。
1
2
3
|
int offset = 10;
List<Integer> nums = Arrays.asList(1, 2, 3);
nums.forEach(n -> System.out.println(n + offset)); // offset 必须是 final
|
2. 不允许修改捕获的局部变量:
1
2
3
4
|
int offset = 10;
List<Integer> nums = Arrays.asList(1, 2, 3);
nums.forEach(n -> System.out.println(n + offset));
offset = 20; // 编译错误:不允许修改捕获的局部变量
|
3. 在 Lambda 表达式中使用可变的局部变量,解决方法:
使用包装类:将变量封装在一个对象中,这样可以在不违反 有效 final 规则的情况下修改对象的属性。
1
2
3
4
5
6
7
8
9
|
class OffsetHolder {
int value;
OffsetHolder(int value) { this.value = value; }
}
OffsetHolder holder = new OffsetHolder(10);
List<Integer> nums = Arrays.asList(1, 2, 3);
nums.forEach(n -> System.out.println(n + holder.value));
holder.value = 20; // 允许修改 holder.value
|
使用数组:将变量放在数组中,数组本身是 有效 final 的,但数组的内容可以修改。
1
2
3
4
|
int[] offsetArray = {10};
List<Integer> nums = Arrays.asList(1, 2, 3);
nums.forEach(n -> System.out.println(n + offsetArray[0]));
offsetArray[0] = 20; // 允许修改数组内容
|
4. 结合 Stream API
Lambda 常用于集合操作:
1
2
3
4
5
|
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> squares = numbers.stream()
.filter(n -> n % 2 == 0) // 过滤偶数
.map(n -> n * n) // 平方
.collect(Collectors.toList()); // 收集结果
|
4. 使用场景
1. 替代匿名内部类
- 如
Runnable
、Comparator
、ClickListener
等接口。
1
2
3
|
// 排序示例
List<String> names = Arrays.asList("Bob", "Alice", "Tom");
names.sort((s1, s2) -> s1.compareTo(s2)); // 按字母顺序排序
|
2. 函数式接口参数
1. 什么是函数式接口
函数式接口(Functional Interface)是 Java 中只包含一个抽象方法的接口。从 Java 8 开始,函数式接口可以使用 Lambda 表达式进行实例化,从而简化代码编写。函数式接口通常用于表示可传递的行为或逻辑
。
- 特点:
- 只有一个抽象方法(可以有多个默认方法和静态方法)。
- 使用 @FunctionalInterface 注解标记(虽然不是必须的,但推荐使用以确保编译器检查)。
2. Java 提供了一些常用的内置函数式接口
接口 |
方法签名 |
用途 |
Runnable |
void run() |
定义无参数、无返回值的任务。 |
Callable |
V call() |
定义无参数、带返回值的任务,支持抛出异常。 |
Consumer |
void accept(T t) |
接受单个输入参数并返回无结果的操作。 |
Supplier |
T get() |
不接受参数但返回结果的函数。 |
Function<T, R> |
R apply(T t) |
接受单个输入参数并返回结果的函数。 |
Predicate |
boolean test(T t) |
接受单个输入参数并返回布尔值的函数,用于条件判断。 |
BiFunction<T, U, R> |
R apply(T t, U u) |
接受两个输入参数并返回结果的函数。 |
示例代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
// Runnable接口
Runnable task = () -> System.out.println("Running a task");
new Thread(task).start();
// Callable接口
Callable<Integer> task = () -> {
System.out.println("Running a task with result");
return 42;
};
FutureTask<Integer> futureTask = new FutureTask<>(task);
new Thread(futureTask).start();
System.out.println("Result: " + futureTask.get());
// Consumer接口
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// 这里定义了一个 Consumer 实例 printName,它接受一个 String 参数并打印出来
Consumer<String> printName = name -> System.out.println(name);
// forEach 方法会遍历 names 列表中的每个元素,并对每个元素调用 printName.accept(name)
names.forEach(printName);
// 显示调用accept方法
for (String name : names) {
printName.accept(name);
}
// Supplier接口
Supplier<String> randomString = () -> UUID.randomUUID().toString();
System.out.println("Random String: " + randomString.get());
// Function接口
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Function<Integer, Integer> square = x -> x * x;
List<Integer> squares = numbers.stream().map(square).collect(Collectors.toList());
System.out.println(squares); // [1, 4, 9, 16, 25]
// Predicate接口
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Predicate<Integer> isEven = x -> x % 2 == 0;
List<Integer> evenNumbers = numbers.stream().filter(isEven).collect(Collectors.toList());
System.out.println(evenNumbers); // [2, 4]
// BiFunction接口
BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
System.out.println("Sum: " + add.apply(3, 5)); // 8
|
3. 函数式接口的参数
函数式接口的参数是指在方法签名中定义的参数类型。这些参数可以是任意类型,包括基本类型、对象类型或其他函数式接口。通过 Lambda 表达式,你可以简洁地传递行为作为参数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public class PredicateExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 使用 Predicate 接口作为参数
filter(numbers, n -> n % 2 == 0); // 过滤偶数
}
public static void filter(List<Integer> list, Predicate<Integer> predicate) {
for (Integer n : list) {
if (predicate.test(n)) {
System.out.print(n + " ");
}
}
System.out.println();
}
}
|
4.定义自定义函数式接口
定义一个自定义函数式接口,只需创建一个接口并在其中声明一个抽象方法。建议使用 @FunctionalInterface 注解来确保接口符合函数式接口的要求。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
@FunctionalInterface
interface MathOperation {
int operate(int a, int b);
}
public class CustomFunctionalInterfaceExample {
public static void main(String[] args) {
// 使用自定义函数式接口
MathOperation addition = (a, b) -> a + b;
MathOperation subtraction = (a, b) -> a - b;
System.out.println("Addition: " + operate(10, 5, addition));
System.out.println("Subtraction: " + operate(10, 5, subtraction));
}
public static int operate(int a, int b, MathOperation operation) {
return operation.operate(a, b);
}
}
|
3. 事件驱动编程
1
|
button.addActionListener(e -> System.out.println("按钮被点击!"));
|
4. 并行处理
1
2
3
4
|
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
int sum = list.parallelStream() // 并行流
.mapToInt(n -> n)
.sum();
|
5. 代码示例
示例 1:线程启动
1
2
3
4
5
6
7
8
9
10
|
// 传统写法
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程运行中");
}
}).start();
// Lambda 写法
new Thread(() -> System.out.println("线程运行中")).start();
|
示例 2:过滤集合
1
2
3
4
|
List<String> languages = Arrays.asList("Java", "Python", "C++", "JavaScript");
List<String> filtered = languages.stream()
.filter(lang -> lang.startsWith("J"))
.collect(Collectors.toList()); // 输出 ["Java", "JavaScript"]
|
示例 3:自定义函数式接口
1
2
3
4
5
6
7
8
9
10
11
12
|
@FunctionalInterface
interface StringProcessor {
String process(String str);
}
public static void main(String[] args) {
StringProcessor upperCase = s -> s.toUpperCase();
StringProcessor reverse = s -> new StringBuilder(s).reverse().toString();
System.out.println(upperCase.process("hello")); // 输出 "HELLO"
System.out.println(reverse.process("hello")); // 输出 "olleh"
}
|
6. 注意事项
- 函数式接口限制:Lambda 只能用于单抽象方法的接口(可通过
@FunctionalInterface
注解标记)。
- 变量捕获限制:Lambda 内部不可修改外部非 final 变量。
- 简洁性优先:避免编写过长的 Lambda 表达式,复杂逻辑可封装为方法。
总结
通过 Lambda 表达式,Java 实现了更简洁的函数式编程风格,特别适合集合操作、异步任务和事件处理等场景,熟练掌握。