lambda表达式是java 8引入的函数式编程特性,用于简化函数式接口的实现;它与匿名内部类的核心区别在于:前者编译不生成独立.class文件、运行时通过invokedynamic动态生成实现类、仅支持单抽象方法接口,后者编译即生成独立.class文件、可实现任意接口或继承类。

匿名内部类和 Lambda 表达式表面看都是“写一块代码传过去”,但底层机制和变量访问规则完全不同。关键不在语法多像,而在 JVM 怎么处理它们。
底层实现:一个真建类,一个延迟生成
匿名内部类在编译时就生成独立的 .class 文件,比如 Outer$1.class。它就是一个实实在在的类,有完整的类结构、字节码、方法表,this 指向自己实例。
Lambda 表达式编译后不产生单独的 class 文件。JVM 用 invokedynamic 指令,在运行时动态生成实现类(可能复用、可能缓存)。它的 this 指向的是外围的外部类实例,不是“自己”的对象。
- 匿名内部类:编译期确定,静态存在,可被反编译看到完整类定义
- Lambda:运行期绑定,字节码里只有引导逻辑,真实类在第一次调用时才出现
对外部变量的访问:都要求 effectively final,但原因不同
两者都只能访问 final 或 effectively final 的局部变量(即定义后没被重新赋值),但限制动因不一样:
- 匿名内部类需要把局部变量“复制一份”进自己的实例字段里,如果变量中途变了,内部副本就跟不上,语义不一致 → 所以强制不可变
- Lambda 虽然也捕获变量,但它不复制字段,而是通过闭包机制引用;JVM 为避免线程间可见性问题和生命周期错乱,同样禁止修改,确保捕获值稳定
注意:成员变量不受此限制。不管是匿名内部类还是 Lambda,都能自由读写 this.xxx,因为它们共享同一个对象实例。
接口兼容性:Lambda 更窄,约束更严
匿名内部类可以实现任意接口(哪怕含多个抽象方法)、继承抽象类甚至普通类;Lambda 只能用于函数式接口(@FunctionalInterface,且仅有一个抽象方法)。
- 接口有 2 个抽象方法?匿名内部类可以写两个
@Override,Lambda 直接编译失败 - 想扩展某个已有类并重写一个方法?只能用匿名内部类,Lambda 不支持继承
变量捕获的实际表现差异
虽然都要求 effectively final,但错误提示和时机略有不同:
- 匿名内部类:只要在类定义之后对变量赋值,编译就报错,检查较早
- Lambda:编译器会扫描整个作用域,包括 Lambda 内部是否尝试修改该变量;哪怕只在 Lambda 体里写
i++,也会直接拒绝 - 两者都不允许修改局部变量,但允许修改成员变量——这点常被忽略,却是调试时的关键线索










