匿名内部类
小崔(后端开发):"白歌,这个
new Runnable() { ... }是创建一个Runnable对象吗?接口不是不能实例化吗?"白歌(架构师):"表面上是
new Runnable(),实际是创建了一个实现Runnable接口的匿名类的实例。编译器会生成一个$1.class文件,你可以反编译看看——它是一个有名字的类(对 JVM 而言),只是对程序员来说'匿名'。"朱璐(前端转 Java):"那 JDK 8 的 Lambda 表达式和匿名内部类是一样的吗?"
白歌:"不完全一样。Lambda 在字节码层面用的是
invokedynamic指令,不生成.class文件;匿名内部类会生成独立的.class文件。作用域也不同——Lambda 里的this指向外部类,匿名内部类的this指向自身。这是面试极爱考的区分点。"
定义与语法
匿名内部类(Anonymous Inner Class) 是没有名字的内部类,它将类的定义和对象的创建合二为一。匿名内部类必须实现一个接口或继承一个类。
语法表格
| 要素 | 说明 |
|---|---|
| 定义方式 | new 接口名() { } 或 new 类名(参数) { } |
| 类名 | 无(编译器自动生成 Outer$N) |
| 构造方法 | 不能定义构造方法(因为没有名字) |
| 继承/实现 | 只能继承一个类 或 实现一个接口(不能同时) |
| 访问局部变量 | 必须 final 或 effectively final |
| .class 文件名 | Outer$1.class、Outer$2.class... |
| 静态成员 | 不能有静态方法或静态字段(除编译期常量) |
| JDK 8 替代 | 函数式接口可用 Lambda 表达式简化 |
基础语法示例
// ========== 实现接口 ==========
Runnable task = new Runnable() { // 不是 new 接口,是 new 匿名类
@Override
public void run() {
System.out.println("匿名内部类实现 Runnable");
}
};
// ========== 继承类 ==========
Thread t = new Thread("飞翔-工作线程") {
@Override
public void run() {
System.out.println(getName() + " 正在执行");
}
};
// ========== 带参数构造 ==========
// 匿名内部类可以调用父类的带参构造方法
new ArrayList<String>(10) {{
add("技术部"); // 实例初始化块
add("市场部");
}};
深度原理分析:字节码揭秘
1. 编译器如何处理匿名内部类
编写以下代码:
public class Outer {
public void test() {
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("匿名内部类");
}
};
}
}
编译后生成两个文件:
Outer.classOuter$1.class→ 就是那个匿名内部类
使用 javap -p Outer$1 查看:
final class Outer$1 implements Runnable {
final Outer this$0; // 持有外部类引用
Outer$1(Outer); // 构造方法接收外部类引用
public void run(); // 实现的方法
}
2. 匿名内部类访问局部变量的字节码
public class Outer {
public void test() {
final String message = "Hello"; // 或 effectively final
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println(message); // 访问局部变量
}
};
}
}
javap -p Outer$1:
final class Outer$1 implements Runnable {
final String val$message; // 拷贝的局部变量
final Outer this$0;
Outer$1(Outer, String); // 构造方法接收局部变量值
}
3. 匿名内部类 vs Lambda:字节码层面的本质区别
// this 区分的经典对比
public class Outer {
private String name = "Outer";
public void test() {
// 匿名内部类 —— this 指向匿名类自身
Runnable anon = new Runnable() {
private String name = "Anonymous";
@Override
public void run() {
System.out.println(this.name); // "Anonymous"
System.out.println(Outer.this.name); // "Outer"
}
};
anon.run(); // 输出 Anonymous, Outer
// Lambda —— this 指向外部类
Runnable lambda = () -> {
System.out.println(this.name); // "Outer" —— this 是 Outer
// System.out.println(Outer.this.name); // 这样写也行,但 this 就直接是 Outer
};
lambda.run(); // 输出 Outer, Outer
}
public static void main(String[] args) {
new Outer().test();
}
}
完整示例
示例一:飞翔科技事件通知系统
import java.util.ArrayList;
import java.util.List;
/**
* 飞翔科技事件总线
* 使用匿名内部类实现事件监听器注册
*/
// 事件接口
interface EventListener {
void onEvent(String event);
}
// 事件总线
class EventBus {
private List<EventListener> listeners = new ArrayList<>();
// 注册监听器
public void register(EventListener listener) {
listeners.add(listener);
}
// 触发事件
public void fireEvent(String event) {
System.out.println("[EventBus] 触发事件: " + event);
for (EventListener listener : listeners) {
listener.onEvent(event);
}
}
}
// 部门服务
class DepartmentService {
private String name;
public DepartmentService(String name) {
this.name = name;
}
public String getName() {
return name;
}
/**
* 注册事件监听 —— 使用匿名内部类
* 每个部门对事件有不同的响应逻辑
*/
public void registerToBus(EventBus bus) {
// ========== 匿名内部类:事件监听器 ==========
bus.register(new EventListener() {
@Override
public void onEvent(String event) {
// 可以访问外部类的 name 字段
System.out.println(" [" + name + "] 收到事件: " + event);
if (event.contains("紧急")) {
System.out.println(" ⚡ " + name + " 启动紧急响应流程");
}
}
});
}
}
public class EventSystemDemo {
public static void main(String[] args) {
EventBus bus = new EventBus();
// 各部门注册监听
DepartmentService tech = new DepartmentService("技术部");
DepartmentService market = new DepartmentService("市场部");
DepartmentService hr = new DepartmentService("人力资源部");
tech.registerToBus(bus);
market.registerToBus(bus);
hr.registerToBus(bus);
// 触发事件
System.out.println("========== 飞翔科技事件通知系统 ==========");
bus.fireEvent("系统维护通知:今晚22:00例行升级");
System.out.println();
bus.fireEvent("紧急:服务器宕机,请相关部门立即处理");
// 查看匿名内部类文件命名
System.out.println();
System.out.println("匿名内部类示例类名: "
+ new EventListener() {
@Override
public void onEvent(String event) {}
}.getClass().getName());
}
}
运行输出:
========== 飞翔科技事件通知系统 ==========
[EventBus] 触发事件: 系统维护通知:今晚22:00例行升级
[技术部] 收到事件: 系统维护通知:今晚22:00例行升级
[市场部] 收到事件: 系统维护通知:今晚22:00例行升级
[人力资源部] 收到事件: 系统维护通知:今晚22:00例行升级
[EventBus] 触发事件: 紧急:服务器宕机,请相关部门立即处理
[技术部] 收到事件: 紧急:服务器宕机,请相关部门立即处理
⚡ 技术部 启动紧急响应流程
[市场部] 收到事件: 紧急:服务器宕机,请相关部门立即处理
⚡ 市场部 启动紧急响应流程
[人力资源部] 收到事件: 紧急:服务器宕机,请相关部门立即处理
⚡ 人力资源部 启动紧急响应流程
匿名内部类示例类名: EventSystemDemo$3
示例二:匿名内部类与Lambda的全面对比
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
/**
* 匿名内部类 vs Lambda 全面对比
* 包含所有关键区分点:this 引用、外部变量遮蔽、字节码等
*/
public class LambdaVsAnonDemo {
private String prefix = "[飞翔科技]";
// ========== 函数式接口定义 ==========
@FunctionalInterface
interface StringProcessor {
String process(String input);
}
// ========== 对比1:基本写法 ==========
public void compareSyntax() {
String input = "Hello World";
// 匿名内部类方式(JDK 1.1+)—— 冗长
StringProcessor anonProcessor = new StringProcessor() {
@Override
public String process(String s) {
return prefix + " " + s.toUpperCase();
}
};
// Lambda 方式(JDK 8+)—— 简洁
StringProcessor lambdaProcessor = s -> prefix + " " + s.toUpperCase();
System.out.println("===== 写法对比 =====");
System.out.println("匿名内部类: " + anonProcessor.process(input));
System.out.println("Lambda: " + lambdaProcessor.process(input));
}
// ========== 对比2:this 引用(关键区分!)==========
public void compareThisReference() {
// 匿名内部类 —— this 指向匿名类实例
Runnable anonRunnable = new Runnable() {
@Override
public void run() {
// this 指向 Runnable 的匿名实现类
System.out.println("匿名内部类 this 的 class: " + this.getClass().getName());
// 需要 Outer.this 来访问外部类
System.out.println(" 外部类 prefix: " + LambdaVsAnonDemo.this.prefix);
}
};
// Lambda —— this 指向外部类实例
Runnable lambdaRunnable = () -> {
// this 就是 LambdaVsAnonDemo 的实例
System.out.println("Lambda this 的 class: " + this.getClass().getName());
System.out.println(" 外部类 prefix: " + this.prefix); // 直接用 this
};
System.out.println("===== this 引用对比 =====");
anonRunnable.run();
System.out.println("---");
lambdaRunnable.run();
}
// ========== 对比3:变量遮蔽 ==========
private String field = "外部类字段";
public void compareVariableShadowing() {
// 匿名内部类可以有自己的字段,遮蔽外部类同名变量
Runnable anon = new Runnable() {
private String field = "匿名内部类字段"; // 遮蔽外部类的 field
@Override
public void run() {
System.out.println("匿名内部类 field: " + this.field); // "匿名内部类字段"
System.out.println("匿名内部类外部 field: " + LambdaVsAnonDemo.this.field); // "外部类字段"
}
};
// Lambda 不能声明自己的字段,不会形成遮蔽
// private String field = "xxx"; // ❌ 编译错误
Runnable lambda = () -> {
System.out.println("Lambda field: " + this.field); // 永远是"外部类字段"
};
System.out.println("===== 变量遮蔽对比 =====");
anon.run();
System.out.println("---");
lambda.run();
}
// ========== 对比4:编译产物 ==========
public void compareBytecode() {
// 匿名内部类:生成 LambdaVsAnonDemo$1.class
Comparator<String> anonComp = new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.length() - b.length();
}
};
// Lambda:不生成 .class 文件,使用 invokedynamic
Comparator<String> lambdaComp = (a, b) -> a.length() - b.length();
List<String> names = Arrays.asList("大翔", "白歌", "小崔");
names.sort(anonComp);
System.out.println("匿名内部类排序: " + names);
names = Arrays.asList("大翔", "白歌", "小崔");
names.sort(lambdaComp);
System.out.println("Lambda 排序: " + names);
System.out.println();
System.out.println("匿名内部类 class: " + anonComp.getClass().getName());
System.out.println("Lambda class: " + lambdaComp.getClass().getName());
}
// ========== 对比5:实例初始化块 ==========
public void compareInitBlock() {
// 匿名内部类可以使用实例初始化块(Lambda 不行)
List<String> departments = new ArrayList<String>() {{
add("技术部");
add("市场部");
add("人力资源部");
add("财务部");
}}; // 双括号初始化 —— 匿名内部类的实例初始化块
System.out.println("===== 双括号初始化(匿名内部类特有) =====");
System.out.println("部门列表: " + departments);
System.out.println("实际类型: " + departments.getClass().getName());
// 注意:departments 的类型不是 ArrayList,而是它的匿名子类!
}
public static void main(String[] args) {
LambdaVsAnonDemo demo = new LambdaVsAnonDemo();
demo.compareSyntax();
System.out.println();
demo.compareThisReference();
System.out.println();
demo.compareVariableShadowing();
System.out.println();
demo.compareBytecode();
System.out.println();
demo.compareInitBlock();
}
}
运行输出:
===== 写法对比 =====
匿名内部类: [飞翔科技] HELLO WORLD
Lambda: [飞翔科技] HELLO WORLD
===== this 引用对比 =====
匿名内部类 this 的 class: LambdaVsAnonDemo$1
外部类 prefix: [飞翔科技]
---
Lambda this 的 class: LambdaVsAnonDemo
外部类 prefix: [飞翔科技]
===== 变量遮蔽对比 =====
匿名内部类 field: 匿名内部类字段
匿名内部类外部 field: 外部类字段
---
Lambda field: 外部类字段
匿名内部类排序: [小崔, 大翔, 白歌]
Lambda 排序: [小崔, 大翔, 白歌]
匿名内部类 class: LambdaVsAnonDemo$4
Lambda class: LambdaVsAnonDemo$$Lambda$1/...
===== 双括号初始化(匿名内部类特有) =====
部门列表: [技术部, 市场部, 人力资源部, 财务部]
实际类型: LambdaVsAnonDemo$5
易错场景
反例一:匿名内部类与 Lambda 混用导致 this 污染
// ❌ 错误:匿名内部类中嵌套 Lambda,this 容易混乱
public class Outer {
private String name = "Outer";
public Runnable createTask() {
Runnable r = new Runnable() {
private String name = "Anonymous";
@Override
public void run() {
// 在匿名内部类中使用 Lambda
Thread t = new Thread(() -> {
System.out.println(this.name); // this 指向谁?
// 答案:匿名内部类实例!
// Lambda 的 this 是定义它的外部范围 —— 这里就是匿名内部类
});
t.start();
}
};
return r;
}
}
// 输出: "Anonymous"(而非 "Outer"),容易造成困惑
纠正:在匿名内部类中需要引用外部外部类时,使用 Outer.this:
Thread t = new Thread(() -> {
System.out.println(Outer.this.name); // 明确引用最外层的 Outer
});
反例二:双括号初始化的隐患
// ⚠️ 看似方便,但有隐患
List<String> list = new ArrayList<String>() {{
add("技术部");
add("市场部");
}};
// 隐患1:创建了 ArrayList 的匿名子类,不是纯粹的 ArrayList
list.getClass().getName(); // "Main$1"
// 隐患2:如果序列化,匿名内部类不可序列化(除非外部类也实现 Serializable)
// 隐患3:持有外部类引用 this$0,可能导致内存泄漏
纠正:JDK 8 优先使用 Arrays.asList() 或 Stream:
// ✅ 更好的方式
List<String> list = Arrays.asList("技术部", "市场部");
// 或
List<String> list = new ArrayList<>(Arrays.asList("技术部", "市场部"));
反例三:匿名内部类中修改非 effectively final 变量
// ❌ 错误:想在匿名内部类中修改外部变量
int count = 0;
Runnable r = new Runnable() {
@Override
public void run() {
count++; // ❌ 编译错误:count 不是 effectively final
}
};
count++; // 这也会导致上面的编译错误
纠正:
// ✅ 使用 AtomicInteger
AtomicInteger count = new AtomicInteger(0); // 引用不变,内容可变
Runnable r = new Runnable() {
@Override
public void run() {
count.incrementAndGet(); // ✅ 可以修改内容
}
};
// ✅ 使用数组
int[] count = {0}; // 引用不变
Runnable r = new Runnable() {
@Override
public void run() {
count[0]++; // ✅ 可以修改元素
}
};
面试考点
Q1:匿名内部类和 Lambda 表达式的本质区别是什么?
① 字节码层面:匿名内部类生成独立的
.class文件(Outer$1.class),Lambda 使用invokedynamic指令在运行时动态生成方法句柄,不产生.class文件; ② this 引用:匿名内部类的this指向自身实例,Lambda 的this指向定义它的外部类实例; ③ 作用域:匿名内部类有独立作用域,可以声明自己的字段和方法,Lambda 使用词法作用域,不能声明新变量; ④ 适用场景:Lambda 只能用于函数式接口(只有一个抽象方法的接口),匿名内部类可以用于任意接口或抽象类。
Q2:为什么匿名内部类不能定义构造方法?
因为构造方法名必须与类名相同,而匿名内部类没有类名。但可以通过以下方式实现类初始化逻辑: ① 实例初始化块(
{ }代码块),编译后会被合并到构造方法中; ② 调用父类的带参构造方法:new ParentClass(arg) { }。
Q3:匿名内部类的双括号初始化 new ArrayList(){{add(...);}} 有什么风险?
① 创建了匿名子类,类型不是
ArrayList本身,序列化、反射等场景下可能出问题; ② 持有外部类引用this$0,可能导致外部类无法被 GC; ③ 破坏了equals()的对称性(匿名子类与ArrayList的equals可能不对称); ④ 如果匿名子类中使用了外部类的实例字段,还可能引发序列化问题。
Q4:匿名内部类可以实现多个接口吗?
不可以。匿名内部类的语法是
new 类名/接口名() { },只能指定一个类型。这是语法限制。如果需要实现多个接口,应使用局部内部类或单独的命名类。这是一个经典的面试陷阱——很多人回答"可以",但混淆了"可以继承一个类并实现接口"(这是局部内部类的特性)。
Q5:JDK 8 中 Lambda 是否完全能替代匿名内部类?
不能完全替代。Lambda 只能用于函数式接口(只有一个抽象方法的接口)。以下场景仍需匿名内部类: ① 需要实现多个方法(非函数式接口); ② 需要继承抽象类或具体类; ③ 需要在内部声明自己的字段或方法; ④ 需要实例初始化块; ⑤ 需要通过
this访问自身实例。 此外,Lambda 不能访问非 effectively final 的变量(和匿名内部类一样),但 Lambda 还不支持自身递归。