目录
[3.1 基本语法](#3.1 基本语法)
[3.2 函数式接口:Lambda的类型](#3.2 函数式接口:Lambda的类型)
[3.3 方法引用](#3.3 方法引用)
[5.1 只能用于函数式接口](#5.1 只能用于函数式接口)
[5.2 变量捕获限制](#5.2 变量捕获限制)
[5.3 this关键字含义不同](#5.3 this关键字含义不同)
[5.4 调试困难](#5.4 调试困难)
[5.5 性能考虑](#5.5 性能考虑)
[5.6 类型推断限制](#5.6 类型推断限制)
[6.1 集合操作](#6.1 集合操作)
[6.2 事件处理](#6.2 事件处理)
[6.3 线程处理](#6.3 线程处理)
[6.4 条件执行](#6.4 条件执行)
1、出现原因
背景与需求
-
代码冗余问题:在Java 8之前,使用匿名内部类实现函数式接口时代码冗长
-
函数式编程趋势:受到函数式语言(如Scala、Python)的影响
-
并行处理需求:多核处理器时代需要更简洁的并发编程方式
-
API设计改进:让集合操作等API更加灵活和表达性强
示例对比
java
// Java 8 之前 - 使用匿名内部类
Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s1.compareTo(s2);
}
});
// Java 8 之后 - 使用Lambda表达式
Collections.sort(list, (s1, s2) -> s1.compareTo(s2));
2、概念
核心定义 :Lambda 表达式是一个匿名函数。
-
匿名:它没有显式的方法名。
-
函数:它像方法一样,有参数列表、一个函数体(执行代码)以及可能的返回值。
-
可传递:Lambda 表达式可以作为参数传递给方法,或者赋值给一个变量。这是它最强大的地方。
主要功能:
-
简化函数式接口的实现
-
支持函数式编程风格
-
提高代码的可读性和简洁性
-
便于并行处理
-
增强集合操作能力
3、使用
3.1 基本语法
java
(parameters) -> expression
// 或
(parameters) -> { statements; }
-
参数列表
(parameters):-
类似于方法的参数列表,放在圆括号中。
-
可以声明参数的类型,但编译器通常可以从上下文中推断出类型,因此大多数时候可以省略。
-
如果只有一个参数,且类型可被推断,圆括号
()也可以省略。 -
如果没有参数,必须使用空括号
()。
-
-
箭头操作符
->:- 它将参数列表与 Lambda 主体分隔开。
-
Lambda 主体
{ ... }:-
包含了要执行的代码。
-
如果主体只有一条语句,花括号
{}和return关键字(如果有返回值)都可以省略。 -
如果主体包含多条语句,则必须使用花括号
{},并且如果需要返回值,必须显式使用return语句。
-
语法示例:
java
// 1. 无参数,返回值类型为 void
() -> System.out.println("Hello World")
// 2. 一个参数,无括号 (Consumer),主体为一条语句
s -> System.out.println(s)
// 3. 一个参数,有括号 (Function)
(s) -> s.length()
// 4. 多个参数,类型推断 (Comparator)
(a, b) -> a.compareTo(b)
// 5. 指定参数类型 (Comparator)
(String a, String b) -> a.compareTo(b)
// 6. 多行代码,需要 return 和 {}
(int a, int b) -> {
int sum = a + b;
return sum;
}
3.2 函数式接口:Lambda的类型
Lambda 表达式不能独立存在,它必须被赋值给一个函数式接口类型的变量。可以把 Lambda 表达式看作是实现了一个函数式接口的、简洁的匿名内部类。
**工作原理:**当在代码中写下一个 Lambda 表达式时,编译器需要知道这个表达式的"目标类型"。这个目标类型必须是一个函数式接口。然后,Lambda 表达式就相当于实现了该接口唯一的抽象方法。
函数式接口的定义 :一个只有一个抽象方法 的接口。关键点:
-
单一抽象方法(SAM):这是最重要的规则。它只能有一个抽象方法。
-
默认方法和静态方法 :函数式接口可以有任意数量的
default方法或static方法。这些有实现的方法不计入"抽象方法"的数量。 -
@FunctionalInterface注解:这个注解不是必须的,但强烈推荐使用。它的作用有:-
编译器检查:告诉编译器"请检查这个接口是否符合函数式接口的定义"。如果不符合(比如没有抽象方法或多个抽象方法),编译器会报错。
-
文档化:让阅读代码的人一目了然地知道这是一个函数式接口,目的是为了配合 Lambda 表达式使用。
-
示例:
java
// 1. 传统的匿名内部类方式
Runnable runnable1 = new Runnable() {
@Override
public void run() { // 实现唯一的抽象方法 'run'
System.out.println("Running...");
}
};
// 2. Lambda 表达式方式
Runnable runnable2 = () -> System.out.println("Running...");
在上面的例子中:
-
Runnable是一个函数式接口(它只有一个run()方法)。 -
Lambda 表达式
() -> System.out.println("Running...")就是run()方法的实现。 -
变量
runnable2的类型是Runnable,这就是 Lambda 表达式的类型。
Java 8 在 java.util.function 包中为我们预定义了大量常用的函数式接口,覆盖了大部分常见场景:
| 接口 | 参数 | 返回值 | 描述 | 抽象方法 | 常见用途 |
|---|---|---|---|---|---|
Consumer<T> |
T | void | 消费型接口 | void accept(T t) |
遍历集合,打印 list.forEach(s -> System.out.println(s)) |
Supplier<T> |
无 | T | 供给型接口 | T get() |
工厂方法 () -> new ArrayList<>() |
Function<T, R> |
T | R | 函数型接口 | R apply(T t) |
类型转换 s -> s.length() |
Predicate<T> |
T | boolean | 断言型接口 | boolean test(T t) |
过滤集合 s -> s.startsWith("A") |
UnaryOperator<T> |
T | T | 一元操作符 | T apply(T t) |
s -> s.toUpperCase() |
BinaryOperator<T> |
(T, T) | T | 二元操作符 | T apply(T t1, T t2) |
(a, b) -> a + b |
java
// Function<T, R>:R apply(T t)
// 数据转换,如将字符串转为整数。
Function<String, Integer> stringToInteger = s -> Integer.parseInt(s);
Integer number = stringToInteger.apply("123"); // 返回 123
// Predicate<T>:boolean test(T t)
// 过滤集合中的元素
Predicate<String> isEmpty = s -> s == null || s.trim().isEmpty();
boolean result = isEmpty.test(" "); // 返回 true
List<String> list = Arrays.asList("A", "BB", "CCC");
List<String> filtered = list.stream().filter(s -> s.length() > 1).toList(); // [BB, CCC]
// Consumer<T>:void accept(T t)
// 遍历集合,打印元素
Consumer<String> printer = s -> System.out.println("Element: " + s);
printer.accept("Hello"); // 打印 "Element: Hello"
list.forEach(printer); // 遍历打印整个 list
// Supplier<T>:T get()
// 惰性求值,对象工厂
Supplier<Double> randomSupplier = () -> Math.random();
Double randomValue = randomSupplier.get(); // 获取一个随机数
// UnaryOperator<T>: 继承自 Function<T, T>,输入和输出类型相同。 T apply(T t)
UnaryOperator<String> toUpper = s -> s.toUpperCase();
// BinaryOperator<T>: 接受两个同类型的参数,返回一个同类型的结果。 T apply(T t1, T t2)
BinaryOperator<Integer> add = (a, b) -> a + b;
// BiFunction<T, U, R>: 接受两个参数 (T, U),返回结果 R。 R apply(T t, U u)
// BiConsumer<T, U>: 接受两个参数 (T, U),不返回结果。 void accept(T t, U u)
自定义函数式接口示例:
java
@FunctionalInterface
interface MyCalculator {
int calculate(int a, int b); // 唯一的抽象方法
}
public class Test {
public static void main(String[] args) {
// 使用 Lambda 表达式实现这个接口
MyCalculator adder = (x, y) -> x + y;
MyCalculator multiplier = (x, y) -> x * y;
System.out.println(adder.calculate(5, 3)); // 输出 8
System.out.println(multiplier.calculate(5, 3)); // 输出 15
}
}
3.3 方法引用
方法引用是 Lambda 表达式的一种更简洁的写法,当 Lambda 体仅仅是调用一个已存在的方法时使用。
语法 :目标对象或类 :: 方法名
java
// 静态方法引用
List<Integer> numbers = Arrays.asList(1, 2, 3);
numbers.forEach(System.out::println);
// 实例方法引用
List<String> strings = Arrays.asList("a", "b", "c");
strings.forEach(String::toUpperCase);
// 构造方法引用
Supplier<List<String>> listSupplier = ArrayList::new;
4、优点
- 代码简洁性
- 函数式编程支持
- 并行处理能力
- 提高可读性
- 延迟执行
java
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 函数式操作
int sum = numbers.stream()
.filter(n -> n % 2 == 0)
.map(n -> n * 2)
.reduce(0, Integer::sum);
List<String> list = Arrays.asList("a", "b", "c");
// 串行处理
list.stream().forEach(System.out::println);
// 并行处理
list.parallelStream().forEach(System.out::println);
// 业务逻辑更清晰
List<Person> adults = people.stream()
.filter(person -> person.getAge() >= 18)
.sorted((p1, p2) -> p1.getName().compareTo(p2.getName()))
.collect(Collectors.toList());
// 延迟执行
Supplier<String> lazyMessage = () -> {
System.out.println("Computing message...");
return "Hello World";
};
// 只有在get()调用时才会执行
String result = lazyMessage.get();
5、限制
5.1 只能用于函数式接口
java
// 正确 - 函数式接口(只有一个抽象方法)
@FunctionalInterface
interface MyFunction {
int operate(int a, int b);
}
MyFunction add = (a, b) -> a + b;
// 错误 - 多个抽象方法的接口不能使用Lambda
interface InvalidInterface {
void method1();
void method2(); // 编译错误
}
5.2 变量捕获限制
java
void outerMethod() {
int count = 0; // 必须是final或等效final
// 错误:在Lambda中修改外部变量
// Runnable r = () -> count++; // 编译错误
// 正确:只能读取final或等效final变量
Runnable r = () -> System.out.println(count);
}
访问局部变量 :Lambda 可以捕获外部方法的局部变量 ,但这些变量必须是 final 或等效 final (effectively final)。等效 final 意味着变量在初始化后值没有再被修改过。
- 原因 :局部变量存在于栈上,而 Lambda 可能在另一个线程中执行,其生命周期可能比创建它的方法更长。为了保证数据一致性,Java 采用了值捕获,即复制变量的值。如果变量可变,就会导致数据不同步。
访问实例变量和静态变量:Lambda 可以自由地读取和修改外部类的实例变量和静态变量,因为它们存储在堆中,是共享的。
5.3 this关键字含义不同
this 关键字 :在 Lambda 表达式内部,this 指的是创建这个 Lambda 表达式的外部类实例 ,而不是 Lambda 表达式本身。这与匿名内部类不同(匿名内部类的 this 指向自身)。
java
public class ThisDemo {
private String value = "Outer class value";
public void testLambda() {
Runnable r = () -> {
// 这里的this指向包含Lambda的外部类实例
System.out.println("this.value: " + this.value);
System.out.println("this.getClass(): " + this.getClass());
// 不能在Lambda内部定义与外部变量同名的变量
// String value = "Lambda value"; // 编译错误
};
r.run();
}
public static void main(String[] args) {
new ThisDemo().testLambda();
}
}
输出结果:
this.value: Outer class value
this.getClass(): class ThisDemo
5.4 调试困难
java
// Lambda表达式在调试时可能显示为匿名方法
List<Integer> numbers = Arrays.asList(1, 2, 3);
numbers.stream()
.map(n -> n * 2) // 调试时可能显示为lambda$0
.forEach(System.out::println);
5.5 性能考虑
java
// 在性能关键场景需要谨慎使用
public void performanceTest() {
// 频繁创建的Lambda可能影响性能
for (int i = 0; i < 1000000; i++) {
Runnable r = () -> System.out.println(i);
// 每次循环都会创建新的Lambda实例
}
}
5.6 类型推断限制
java
// 在某些复杂情况下需要显式类型声明
// 编译错误:类型推断失败
// BinaryOperator add = (x, y) -> x + y;
// 正确:显式声明类型
BinaryOperator<Integer> add = (Integer x, Integer y) -> x + y;
6、应用场景
6.1 集合操作
java
List<User> users = getUserList();
// 过滤、转换、收集
List<String> names = users.stream()
.filter(user -> user.getAge() > 18)
.map(User::getName)
.sorted()
.collect(Collectors.toList());
6.2 事件处理
java
// Swing事件处理
JButton button = new JButton("Click");
button.addActionListener(e -> {
System.out.println("Button clicked");
performAction();
});
6.3 线程处理
java
// 创建线程
new Thread(() -> {
// 后台任务
processData();
updateUI();
}).start();
6.4 条件执行
java
// 延迟执行和条件检查
public void processIfValid(Supplier<Boolean> validator,
Runnable processor) {
if (validator.get()) {
processor.run();
}
}
// 使用
processIfValid(() -> checkCondition(), () -> doWork());