Lambda表达式
- [Lambda 解决了什么问题](#Lambda 解决了什么问题)
- [Lambda 语法](#Lambda 语法)
- [函数式接口:Lambda 的载体⭐](#函数式接口:Lambda 的载体⭐)
- [Lambda 与匿名类的 this ⭐](#Lambda 与匿名类的 this ⭐)
- Lambda的变量捕获⭐
- Lambda在集合当中的使用⭐
-
- [Collection 新增方法](#Collection 新增方法)
- [List 新增方法](#List 新增方法)
- [Map 新增方法](#Map 新增方法)
- [方法引用:让 Lambda 更简洁](#方法引用:让 Lambda 更简洁)
- [Lambda + Stream](#Lambda + Stream)
- [Lambda 异常处理](#Lambda 异常处理)
-
- [内部 try-catch(推荐简单场景)](#内部 try-catch(推荐简单场景))
- 自定义抛出异常的函数式接口
- 通用包装器工具类(工程实践首选)
- 笔试面试高频题
-
- [Lambda 表达式基础语法](#Lambda 表达式基础语法)
- [写出以下 Lambda 表达式等价的匿名内部类](#写出以下 Lambda 表达式等价的匿名内部类)
- 函数式接口识别⭐
- 变量捕获题⭐
- this识别⭐
- [Lambda 表达式和匿名内部类有什么区别?⭐](#Lambda 表达式和匿名内部类有什么区别?⭐)
- [为什么 Lambda 中引用的局部变量必须是 final 或 effectively final?⭐](#为什么 Lambda 中引用的局部变量必须是 final 或 effectively final?⭐)
- [Stream + Lambda 综合题](#Stream + Lambda 综合题)
Lambda 解决了什么问题
回想一下用匿名类实现 Comparator 的场景:
java
// Java 8 之前的写法:为了一行逻辑写了六行代码
Collections.sort(list, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;
}
});
真正有用的其实只有 o1 - o2 这个比较逻辑,其他全是语法噪音。Lambda 让这一切变得简洁:
java
// Lambda 写法:逻辑一目了然
Collections.sort(list, (o1, o2) -> o1 - o2);
这种将行为作为参数传递的能力,正是函数式编程的核心思想。当代码中遍布此类操作时,Lambda 带来的简洁性会被成倍放大。
Lambda 语法
Lambda 表达式由三部分组成:参数列表 + 箭头符号 -> + 函数体:
| 参数情况 | 有返回值 | 无返回值(void) |
|---|---|---|
| 无参 | () -> 结果 () -> { return 结果; } |
() -> 动作 () -> { 动作; } |
| 单参 | x -> 结果 (x) -> 结果 |
x -> 动作 (x) -> 动作 |
| 多参 | (x, y) -> 结果 (x, y) -> { return 结果; } |
(x, y) -> 动作 (x, y) -> { 动作; } |
语法精简
- 参数类型可省略:编译器根据上下文自动推断(类型推断),如果需要省略,每个参数的类型都要省略。
- 单参数可省略括号 :
x -> x * 2等价于(x) -> x * 2 - 单表达式可省略 return 和大括号 :
(a, b) -> a + b自动返回表达式结果
java
// 无参 + 有返回值
() -> 42
() -> { return 42; }
// 无参 + 无返回值
() -> System.out.println("Hi")
() -> { System.out.println("Hi"); }
// 单参 + 有返回值
x -> x * 2
(x) -> { return x * 2; }
// 单参 + 无返回值
x -> System.out.println(x)
(x) -> { System.out.println(x); }
// 多参 + 有返回值
(a, b) -> a + b
(a, b) -> { return a + b; }
// 多参 + 无返回值
(a, b) -> System.out.println(a + b)
(a, b) -> { System.out.println(a + b); }
函数式接口:Lambda 的载体⭐
@FunctionalInterface⭐
Lambda 表达式本身必须依附于一个函数式接口 。所谓函数式接口,就是只有一个抽象方法的接口。
一个接口要成为函数式接口,可以在接口上写注解@FunctionalInterface,告诉编译器,这个接口必须满足以下条件:
-
唯一的抽象方法: 接口中只能声明一个没有方法体的方法。
-
可以包含其他方法: 允许有任意数量的 default(默认方法) 或 static(静态方法),因为它们已经有了具体实现。
-
Object 类方法: 重写 Object 类的方法(如 equals())不计入抽象方法计数。
java
@FunctionalInterface
public interface MyChecker {
// 唯一的抽象方法
boolean test(int value);
// 允许有默认方法
default void log(int value) {
System.out.println("Checking value: " + value);
}
// 编译报错:如果再取消下面注释,就会报错,因为有两个抽象方法了
// void clear();
}
函数式接口的使用⭐
java
@FunctionalInterface
public interface Calculator {
int operate(int a, int b);
}
// Lambda 实现了这个唯一的抽象方法
Calculator add = (a, b) -> a + b;
Calculator multiply = (a, b) -> a * b;
等价的匿名内部类 (Anonymous Inner Class) 写法如下,只是等价的写法两者并不完全一样
java
// 1. 加法操作的匿名内部类写法
Calculator add = new Calculator() {
@Override
public int operate(int a, int b) {
return a + b;
}
};
// 2. 乘法操作的匿名内部类写法
Calculator multiply = new Calculator() {
@Override
public int operate(int a, int b) {
return a * b;
}
};
常见的内置函数式接口⭐
Java 8 在 java.util.function 包中内置了四大核心函数式接口,覆盖了绝大多数使用场景:
| 接口名 | 抽象方法 | 描述 | 应用场景 |
|---|---|---|---|
Predicate<T> |
boolean test(T t) |
接收一个参数,返回布尔值 | 数据过滤、条件校验 |
Consumer<T> |
void accept(T t) |
接收参数,不返回值 | 日志记录、数据输出、修改对象状态 |
Function<T, R> |
R apply(T t) |
接收 T,返回 R | 类型转换、属性提取 |
Supplier<T> |
T get() |
不接收参数,返回结果 | 懒加载、初始化对象、生成随机数 |
Runnable |
void run() |
执行无参无返回的任务 | 线程执行、异步任务 |
Callable<V> |
V call() |
执行任务并返回值 | 带返回值的异步任务 |
Comparator<T> |
int compare(T o1, T o2) |
比较两个对象 | 排序比较、优先级判断 |
java
// 接收一个字符串 s,判断其长度是否大于 5,返回布尔值。
// Lambda 写法
Predicate<String> isLong = s -> s.length() > 5;
// 匿名内部类等价写法
Predicate<String> isLongAnon = new Predicate<String>() {
@Override
public boolean test(String s) {
return s.length() > 5;
}
};
java
//接收一个字符串 s,直接执行打印操作,没有返回值。
// Lambda 写法
Consumer<String> printer = s -> System.out.println(s);
// 匿名内部类等价写法
Consumer<String> printerAnon = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
java
// 接收一个字符串 s,将其映射(转换)为它的长度(Integer 类型)。
// Lambda 写法
Function<String, Integer> length = s -> s.length();
// 匿名内部类等价写法
Function<String, Integer> lengthAnon = new Function<String, Integer>() {
@Override
public Integer apply(String s) {
return s.length();
}
};
java
//不需要任何输入参数,每次调用时返回一个随机的 Double 值。
// Lambda 写法
Supplier<Double> random = () -> Math.random();
// 匿名内部类等价写法
Supplier<Double> randomAnon = new Supplier<Double>() {
@Override
public Double get() {
return Math.random();
}
};
java
// Runnable:线程执行
// Lambda 写法
new Thread(() -> System.out.println("新线程运行中")).start();
// Runnable 的匿名内部类等价写法
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("新线程运行中");
}
}).start();
java
// Comparator:排序比较
// Lambda 写法
List<String> list = Arrays.asList("apple", "banana", "cat");
list.sort((a, b) -> a.length() - b.length()); // 按长度排序
// Comparator 的匿名内部类等价写法
List<String> list = Arrays.asList("apple", "banana", "cat");
list.sort(new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.length() - b.length();
}
});
Lambda 与匿名类的 this ⭐
在 Java 中,Lambda 表达式并不是匿名内部类的语法糖,它们在作用域(Scope)的处理上完全不同。
| 维度 | 匿名内部类 | Lambda 表达式 |
|---|---|---|
this 的含义 |
指向内部类对象本身。 | 指向外层类对象 (和外层方法内的 this 一模一样)。 |
| 作用域 | 引入了新的作用域 。可以在内部声明和 this 绑定的变量。 |
处于词法作用域中。它没有自己的作用域,而是共享外层的。 |
| 变量遮蔽 | 内部定义的变量会遮蔽外部类的同名变量。 | 不能在 Lambda 参数中定义与局部变量同名的变量(会编译报错)。 |
通过下面的例子,可以清晰地看到 this 的指向变化:
java
public class ThisDemo {
private String name = "外部类实例变量";
public void test() {
// 1. 匿名内部类实现
Runnable anonRunnable = new Runnable() {
String name = "匿名内部类局部变量";
@Override
public void run() {
// 此处的 this 指向的是 new Runnable() 产生的对象
System.out.println("匿名内部类中的 this: " + this.getClass().getSimpleName());
System.out.println("匿名内部类访问自己的变量: " + this.name);
}
};
// 2. Lambda 表达式实现
Runnable lambdaRunnable = () -> {
// 此处的 this 指向的是外部的 ThisDemo 实例
// Lambda 内部甚至不能直接定义一个叫 name 的成员变量(除非是局部变量)
System.out.println("Lambda 中的 this: " + this.getClass().getSimpleName());
System.out.println("Lambda 访问外部类的变量: " + this.name);
};
new Thread(anonRunnable).start();
new Thread(lambdaRunnable).start();
}
public static void main(String[] args) {
new ThisDemo().test();
}
}
输出结果
匿名内部类中的 this: Test0414.ThisDemo$1
匿名内部类访问自己的变量: 匿名内部类局部变量
Lambda 中的 this: ThisDemo
Lambda 访问外部类的变量: 外部类实例变量
Lambda的变量捕获⭐
Lambda 表达式可以访问外部的局部变量和成员变量,但有严格的限制。
| 变量类型 | 限制 | 原因 |
|---|---|---|
| 局部变量 | 初始化后不再修改(effectively final) | 局部变量存于栈,Lambda 可能异步执行,复制值保证一致性 |
| 实例成员变量 | 无限制,可修改 | 存于堆,Lambda 通过 this 引用访问 |
| 静态成员变量 | 无限制,可修改 | 全局共享,生命周期与类相同 |
java
public class LambdaCaptureDemo {
// ✅ 正确:变量初始化后不再修改(effectively final)
public void validCapture() {
int x = 10;
Runnable r = () -> System.out.println(x);
r.run(); // 输出 10
}
// ❌ 错误:变量被重新赋值,破坏了 effectively final
public void invalidCapture() {
int y = 20;
y = 30; // 重新赋值
// Runnable r = () -> System.out.println(y); // 编译报错
}
// ✅ 实例变量无限制,可读可写
private int count = 0;
public void instanceVariable() {
Runnable r = () -> {
count++; // 可以修改
System.out.println(count);
};
r.run();
}
}
为什么有这个限制?
Lambda 可能在不同线程、不同时机执行。如果允许修改局部变量,会出现数据不一致问题。Java 选择让 Lambda 持有变量的副本,强制原变量不可变来保证安全。
Lambda在集合当中的使用⭐
Java 8 为配合 Lambda 表达式,在集合框架中新增的方法。
Collection 新增方法
| 方法 | 参数类型 | 作用 |
|---|---|---|
removeIf(Predicate<? super E> filter) |
Predicate |
批量删除符合条件的元素 |
stream() |
无 | 返回顺序流 |
parallelStream() |
无 | 返回并行流 |
spliterator() |
无 | 返回可分割迭代器(用于并行遍历) |
forEach(Consumer<? super E> action) |
Consumer |
内部迭代遍历元素 |
java
// Collection.removeIf():删除偶数
List<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
numbers.removeIf(n -> n % 2 == 0); // [1, 3, 5]
// Collection.forEach
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.forEach(s -> System.out.println("Hello, " + s));
List 新增方法
| 方法 | 参数类型 | 作用 |
|---|---|---|
replaceAll(UnaryOperator<E> operator) |
UnaryOperator |
对每个元素应用操作并替换 |
sort(Comparator<? super E> c) |
Comparator |
按指定规则排序 |
java
// List.replaceAll():所有元素转大写
List<String> words = Arrays.asList("hello", "world");
words.replaceAll(s -> s.toUpperCase()); // ["HELLO", "WORLD"]
// List.sort():按长度排序
words.sort((a, b) -> a.length() - b.length());
Map 新增方法
| 方法 | 参数类型 | 作用 |
|---|---|---|
getOrDefault(Object key, V defaultValue) |
无函数式接口 | 取值,不存在时返回默认值 |
forEach(BiConsumer<? super K, ? super V> action) |
BiConsumer |
遍历键值对 |
replaceAll(BiFunction<? super K, ? super V, ? extends V> function) |
BiFunction |
对每个键值对应用操作并替换值 |
putIfAbsent(K key, V value) |
无函数式接口 | 键不存在时才放入 |
remove(Object key, Object value) |
无函数式接口 | 键值都匹配时才删除 |
replace(K key, V oldValue, V newValue) |
无函数式接口 | 键和旧值都匹配时才替换 |
replace(K key, V value) |
无函数式接口 | 键存在时才替换值 |
computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) |
Function |
键不存在时计算值并放入 |
computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) |
BiFunction |
键存在时重新计算值 |
compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) |
BiFunction |
无论键是否存在都重新计算 |
merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) |
BiFunction |
合并新旧值 |
java
// Map.forEach():遍历键值对
Map<String, Integer> map = new HashMap<>();
map.put("apple", 1);
map.put("banana", 2);
map.forEach((k, v) -> System.out.println(k + " = " + v));
// Map.computeIfAbsent():键不存在时计算放入
map.computeIfAbsent("cherry", k -> k.length()); // "cherry" = 6
// Map.merge():合并值
map.merge("apple", 1, (oldVal, newVal) -> oldVal + newVal); // "apple" = 2
方法引用:让 Lambda 更简洁
当 Lambda 表达式的逻辑仅仅是调用一个已有方法时,方法引用更简洁:
| 类型 | 语法 | Lambda 等价形式 |
|---|---|---|
| 静态方法引用 | Integer::parseInt |
s -> Integer.parseInt(s) |
| 实例方法引用 | System.out::println |
x -> System.out.println(x) |
| 对象方法引用 | String::length |
s -> s.length() |
| 构造器引用 | ArrayList::new |
() -> new ArrayList() |
java
// 方法引用让代码更加声明式
List<String> words = Arrays.asList("apple", "banana", "cherry");
words.forEach(System.out::println); // 比 x -> System.out.println(x) 更干净
Lambda + Stream
Lambda 的真正威力在与 Stream API 结合时彻底释放。Stream 提供了一套声明式的数据处理流水线,配合 Lambda 让集合操作变得行云流水:
java
List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5, 9, 2, 6);
// 需求:找出所有大于3的偶数,排序后取前两个
List<Integer> result = numbers.stream()
.filter(n -> n > 3) // 过滤:Predicate
.filter(n -> n % 2 == 0) // 再次过滤
.sorted() // 排序
.limit(2) // 截取
.collect(Collectors.toList()); // 收集结果
// 用传统循环写这段逻辑,代码量至少翻倍
Stream 操作分为两类:
- 中间操作 (Intermediate):
filter、map、sorted等,返回新 Stream,惰性执行 - 终端操作 (Terminal):
collect、forEach、count等,触发实际计算
Lambda 异常处理
Java 的函数式编程有一个不大不小的痛点:java.util.function 包中的标准接口(如 Function、Consumer、Predicate 等)的抽象方法没有声明 throws。
这意味着,当 Lambda 表达式中需要抛出受检异常(Checked Exception)时,编译器会直接报错。
假设我们有一个操作:读取文件的第一行,并转换为大写。传统写法:
java
// 传统写法:正常抛出异常
public String readFirstLine(File file) throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader(file))) {
return br.readLine().toUpperCase();
}
}
把这个逻辑封装成一个 Function 传递给 Stream 的 map 操作时,问题来了:
java
List<File> files = Arrays.asList(new File("a.txt"), new File("b.txt"));
// ❌ 编译错误:未报告的异常 java.io.IOException
files.stream()
.map(file -> {
BufferedReader br = new BufferedReader(new FileReader(file));
return br.readLine().toUpperCase();
})
.collect(Collectors.toList());
编译器会提示:IOException 是受检异常,而 Function 的 apply 方法没有声明抛出任何异常。
内部 try-catch(推荐简单场景)
最直接的方式是在 Lambda 内部捕获并处理异常。这适合能够就地处理异常的场景。
java
// 方案1:内部捕获,转换为运行时异常
files.stream()
.map(file -> {
try (BufferedReader br = new BufferedReader(new FileReader(file))) {
return br.readLine().toUpperCase();
} catch (IOException e) {
// 根据业务决定是抛运行时异常还是返回默认值
throw new RuntimeException("读取文件失败: " + file, e);
}
})
.collect(Collectors.toList());
优点 :简单直接,无需额外定义接口。
缺点:代码臃肿,原本一行 Lambda 被 try-catch 撑到了四行;而且检查异常被包装成了运行时异常,调用方无法感知原始异常类型。
自定义抛出异常的函数式接口
如果想保持 Lambda 的简洁性,并保留受检异常的类型信息,可以自定义允许抛出异常的函数式接口。
java
// 定义允许抛出异常的 Function 接口
@FunctionalInterface
interface ThrowingFunction<T, R> {
R apply(T t) throws Exception;
}
直接使用这个接口:
java
ThrowingFunction<File, String> readFirstLine = file -> {
try (BufferedReader br = new BufferedReader(new FileReader(file))) {
return br.readLine().toUpperCase();
}
};
// 调用时需要处理 Exception
String result = readFirstLine.apply(new File("a.txt"));
但这只能在显式调用时使用,不能直接传给 Stream 的 map (因为 map 要求标准的 Function)。
通用包装器工具类(工程实践首选)
编写一个工具方法,将抛异常的 Lambda 包装成标准接口,并在包装层统一处理异常策略。
java
public class LambdaExceptionUtils {
@FunctionalInterface
public interface ThrowingFunction<T, R> {
R apply(T t) throws Exception;
}
/**
* 将抛出受检异常的 Function 包装为标准 Function
* @param throwingFunction 原始抛异常的函数
* @param <T> 输入类型
* @param <R> 返回类型
* @return 标准 Function,异常时抛出 RuntimeException
*/
public static <T, R> Function<T, R> wrap(ThrowingFunction<T, R> throwingFunction) {
return t -> {
try {
return throwingFunction.apply(t);
} catch (Exception e) {
throw new RuntimeException(e);
}
};
}
}
使用示例:
java
List<File> files = Arrays.asList(new File("a.txt"), new File("b.txt"));
List<String> firstLines = files.stream()
.map(LambdaExceptionUtils.wrap(file -> {
try (BufferedReader br = new BufferedReader(new FileReader(file))) {
return br.readLine().toUpperCase();
}
}))
.collect(Collectors.toList());
进阶版:支持多种异常策略
java
public class LambdaExceptionUtils {
// 策略1:抛出运行时异常
public static <T, R> Function<T, R> wrapRuntime(ThrowingFunction<T, R> f) {
return t -> {
try { return f.apply(t); }
catch (Exception e) { throw new RuntimeException(e); }
};
}
// 策略2:异常时返回默认值
public static <T, R> Function<T, R> wrapWithDefault(ThrowingFunction<T, R> f, R defaultValue) {
return t -> {
try { return f.apply(t); }
catch (Exception e) {
System.err.println("操作失败,返回默认值: " + e.getMessage());
return defaultValue;
}
};
}
// 策略3:异常时返回 Optional
public static <T, R> Function<T, Optional<R>> wrapOptional(ThrowingFunction<T, R> f) {
return t -> {
try { return Optional.ofNullable(f.apply(t)); }
catch (Exception e) { return Optional.empty(); }
};
}
}
笔试面试高频题
Lambda 表达式基础语法
java
// ①
() -> {}
// ②
x -> return x * 2;
// ③
(x, y) -> { x + y; }
// ④
(String s) -> s.length()
// ⑤
x, y -> x + y
// ⑥
() -> { return 42; }
答案
java
// ① ✅ 正确,无参无返回值
() -> {}
// ② ❌ 错误,单表达式不能用 return,应为 x -> x * 2
x -> return x * 2;
// ③ ❌ 错误,语句块中需要 return,应为 (x, y) -> { return x + y; }
(x, y) -> { x + y; }
// ④ ✅ 正确,单参带类型声明,单表达式自动返回
(String s) -> s.length()
// ⑤ ❌ 错误,多参必须加括号,应为 (x, y) -> x + y
x, y -> x + y
// ⑥ ✅ 正确,语句块中显式 return
() -> { return 42; }
写出以下 Lambda 表达式等价的匿名内部类
java
Runnable r = () -> System.out.println("Hello");
Comparator<String> c = (s1, s2) -> s1.length() - s2.length();
答案
java
// Runnable 匿名内部类
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("Hello");
}
};
// Comparator 匿名内部类
Comparator<String> c = new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s1.length() - s2.length();
}
};
函数式接口识别⭐
java
// A
interface A {
void doSomething();
}
// B
interface B {
void method1();
void method2();
}
// C
interface C {
void method();
default void defaultMethod() {}
static void staticMethod() {}
}
// D
interface D {
boolean equals(Object obj);
void execute();
}
// E
@FunctionalInterface
interface E {
void run();
// void stop(); // 取消注释会编译报错
}
A :✅ 只有一个抽象方法
B :❌ 两个抽象方法
C :✅ 只有一个抽象方法,default 和 static 方法不计数
D :❌equals是 Object 的方法,不计数,但execute算一个,所以是函数式接口?等一等 ------equals来自 Object,不计入抽象方法数,所以 D 只有一个抽象方法execute,是函数式接口
E :✅ 有@FunctionalInterface且只有一个抽象方法
关键知识点 :函数式接口 = 只有一个抽象方法的接口。Object 类的 public 方法(equals、hashCode、toString 等)不计入。
变量捕获题⭐
以下代码能否编译通过?如果不能,说明原因。
java
public class CaptureTest {
public void test() {
int a = 10;
Runnable r1 = () -> System.out.println(a);
int b = 20;
b = 30;
Runnable r2 = () -> System.out.println(b);
int c = 40;
Runnable r3 = () -> {
// c++; // 取消注释会怎样?
System.out.println(c);
};
}
}
r1:✅ 编译通过,变量a是 effectively final
r2:❌ 编译报错,b被重新赋值,不是 effectively final
r3:如果取消注释c++,❌ 编译报错,不能修改局部变量
核心考点 :Lambda 捕获的局部变量必须是 effectively final(初始化后不再修改)。
this识别⭐
写出以下代码的输出结果。
java
public class ThisTest {
private String name = "Outer";
public void test() {
// 匿名内部类
new Thread(new Runnable() {
private String name = "Inner";
@Override
public void run() {
System.out.println(this.name);
System.out.println(ThisTest.this.name);
}
}).start();
// Lambda
new Thread(() -> {
System.out.println(this.name);
}).start();
}
public static void main(String[] args) {
new ThisTest().test();
}
}
答案
text
Inner
Outer
Outer
匿名内部类中,
this指向内部类实例,所以this.name输出"Inner"匿名内部类中,
ThisTest.this指向外部类实例,输出"Outer"Lambda 中,
this直接指向外部类实例,输出"Outer"
核心考点 :Lambda 中的 this 指向外层实例,匿名内部类的 this 指向自身。
Lambda 表达式和匿名内部类有什么区别?⭐
| 区别点 | Lambda | 匿名内部类 |
|---|---|---|
| 实现机制 | 通过 invokedynamic 动态生成,不产生独立 .class 文件 |
编译后生成 Outer$1.class 文件 |
| this 指向 | 指向外层类实例 | 指向匿名内部类自身实例 |
| 变量作用域 | 不能定义与外部同名的局部变量(无遮蔽) | 可以定义成员变量,会遮蔽外部同名变量 |
| 适用范围 | 只能是函数式接口 | 任意接口或抽象类 |
| 代码简洁度 | 极简 | 模板代码多 |
| 性能 | 通常优于匿名内部类(无类加载开销) | 有类加载和实例化开销 |
为什么 Lambda 中引用的局部变量必须是 final 或 effectively final?⭐
主要原因是线程安全 和内存可见性:
- 局部变量生命周期:局部变量存储在栈上,方法执行完就销毁。Lambda 可能被传递到其他线程异步执行,此时原方法的栈帧已销毁,变量不存在了。
- 实现机制 :Lambda 实际上是对捕获变量的值进行复制,而非引用。如果不强制 final,会出现"改了值但 Lambda 看不到"或"Lambda 改了值外面看不到"的混乱情况。
- 并发安全:强制不可变避免了多线程环境下的数据竞争问题,符合函数式编程的不可变理念。
Stream + Lambda 综合题
给定 List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10),用一行 Stream 实现:
- ① 求所有偶数的平方和
- ② 找出所有大于 5 的奇数,按降序排列
- ③ 将列表按奇偶分组
答案
java
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// ① 所有偶数的平方和
int sum = numbers.stream()
.filter(n -> n % 2 == 0)
.map(n -> n * n)
.reduce(0, Integer::sum);
// 或者 .mapToInt(n -> n * n).sum()
// ② 大于5的奇数,降序排列
List<Integer> result = numbers.stream()
.filter(n -> n > 5 && n % 2 == 1)
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList());
// ③ 按奇偶分组
Map<Boolean, List<Integer>> groups = numbers.stream()
.collect(Collectors.partitioningBy(n -> n % 2 == 0));
给定 List<String> words = Arrays.asList("apple", "banana", "cat", "dog", "elephant"),用 Stream 实现:
- ① 统计每个单词的长度,输出
Map<String, Integer> - ② 找出最长的单词(如果有多个取第一个)
- ③ 将所有单词按首字母分组
答案
java
List<String> words = Arrays.asList("apple", "banana", "cat", "dog", "elephant");
// ① 单词 -> 长度
Map<String, Integer> lengthMap = words.stream()
.collect(Collectors.toMap(Function.identity(), String::length));
// ② 最长的单词
String longest = words.stream()
.max(Comparator.comparing(String::length))
.orElse(null);
// ③ 按首字母分组
Map<Character, List<String>> groupByFirst = words.stream()
.collect(Collectors.groupingBy(s -> s.charAt(0)));