Lambda表达式与匿名内部类的对比详解
1. 语法简洁性
-
Lambda表达式:
仅适用于函数式接口(只有一个抽象方法的接口),语法简洁。
示例:Runnable r = () -> System.out.println("Hello Lambda");
-
匿名内部类:
适用于任何接口或抽象类,需完整定义类结构。
示例:Runnable r = new Runnable() {@Overridepublic void run() {System.out.println("Hello Anonymous Class");} };
关键差异:
Lambda省去了接口名、方法名和new
关键字,代码更紧凑。
2. this指向
-
Lambda表达式:
this
指向包围Lambda的类实例。
示例:public class Outer {public void method() {Runnable r = () -> System.out.println(this); // 输出Outer实例} }
-
匿名内部类:
this
指向匿名内部类自身的实例。
示例:public class Outer {public void method() {Runnable r = new Runnable() {@Overridepublic void run() {System.out.println(this); // 输出匿名内部类实例}};} }
3. 编译与性能
-
Lambda表达式:
- 编译时生成
invokedynamic
指令,运行时动态生成实现类。编译之后,没有生成一个单独的.class字节码文件。 - 性能优化:JVM缓存Lambda实例,避免重复创建(如单例模式)。
- 编译时生成
-
匿名内部类:
- 编译时生成独立的
.class
文件(如Outer$1.class
)。 - 性能开销:每次实例化均创建新对象。
- 编译时生成独立的
验证方法:
运行后检查项目目录下的target/classes
或out/production
文件夹,观察生成的类文件。
4. 变量捕获
-
共同规则:
只能访问final
或**等效final(effectively final)**的局部变量。 -
Lambda表达式:
捕获的变量在Lambda内部隐式视为final
,修改会编译报错。
示例:int count = 0; Runnable r = () -> count++; // 错误:count必须为final或等效final
-
匿名内部类:
规则相同,但错误提示更明确。
示例:int count = 0; Runnable r = new Runnable() {@Overridepublic void run() {count++; // 错误:从内部类引用的局部变量必须是final或等效final} };
5. 应用场景
-
优先使用Lambda的场景:
- 实现函数式接口(如
Runnable
、Comparator
),Lambda表达式,只能是接口。 - 需要简洁的代码(如Stream API、事件处理器)。
- 性能敏感的重复实例创建场景。
- 实现函数式接口(如
-
必须使用匿名内部类的场景:
- 实现非函数式接口(含多个抽象方法)。
- 需要覆盖接口的默认方法或访问protected方法。
- 需要继承具体类或抽象类。
示例对比:
// Lambda:仅适用于单方法接口
Function<String, Integer> lengthFunc = s -> s.length();// 匿名内部类:可覆盖默认方法
List<String> list = new ArrayList<>() {@Overridepublic boolean add(String s) {System.out.println("添加元素: " + s);return super.add(s);}
};
6. 内存与类加载
-
Lambda表达式:
- 无额外类文件,减少PermGen/Metaspace内存占用。
- JVM内部通过
LambdaMetafactory
动态生成实现。
-
匿名内部类:
- 每个类生成独立的
.class
文件,增加元空间负担。 - 类加载器需加载更多类,影响启动速度。
- 每个类生成独立的
7. 总结对比表
特性 | Lambda表达式 | 匿名内部类 |
---|---|---|
适用接口 | 仅函数式接口 | 任意接口或抽象类 |
语法简洁性 | 高 | 低 |
this指向 | 外部类实例 | 内部类自身实例 |
类文件生成 | 无独立类文件 | 生成Outer$1.class 文件 |
性能开销 | 低(实例可缓存) | 较高(每次实例化新对象) |
变量捕获 | 等效final | 等效final |
覆盖方法 | 不支持 | 支持(可覆盖默认方法) |
何时选择Lambda或匿名内部类?
-
选择Lambda:
- 实现函数式接口且无需覆盖默认方法。
- 追求代码简洁性和性能优化。
- 示例:
Thread
启动、集合排序、Stream操作。
-
选择匿名内部类:
- 需实现多方法接口或抽象类。
- 需覆盖接口的默认方法或调用父类protected方法。
- 示例:自定义
List
实现、GUI事件监听器(如Swing中的ActionListener
)。