匿名类
1️⃣ 本质:一次性定义的匿名子类对象
匿名类在语法上表现为 new 接口/类(){...},但实际上做了两件事:
- 隐式定义了一个子类:继承指定类或实现指定接口,类名由编译器自动生成(如
Outer$1)。 - 立即创建该子类的一个实例,并返回引用。
Runnable r = new Runnable() {
@Override
public void run() { System.out.println("匿名子类对象"); }
};
// r 的实际类型是编译器生成的匿名子类(如 Demo$1)2️⃣ 对象的编译时类型 vs 运行时类型
- 编译时类型:声明时使用的类型(父类或接口),例如
Runnable。 - 运行时类型:实际创建的匿名子类,例如
class Demo$1。
System.out.println(r.getClass().getName()); // 输出类似 Demo$1这种多态性意味着:匿名类对象只能通过父类/接口引用访问其方法,不能使用匿名类内部新增的成员(除非强制转型,但很不安全且不推荐)。
3️⃣ 匿名类作为“子类”的特点
- 继承父类/实现接口:匿名类一定是某个已有类或接口的子类。
- 没有类名:无法在其他地方重复使用该子类定义,也无法
extends或implements它。 - 可以重写父类方法:就像普通子类一样,可重写父类的非 final 方法或接口的抽象方法。
- 不能定义静态成员(除
static final常量外),因为匿名类本质上是局部内部类。 - 不能有构造器:但可以用实例初始化块模拟构造功能。
匿名类实例:
new 父类名(){类体}(是一种子类对象)
new Object() {
{ // 实例初始化块,在构造时执行
System.out.println("匿名子类初始化");
}
};4️⃣ 对象的创建与生命周期
- 创建方式:
new 接口/父类() { ... }一步完成定义和实例化。 - 唯一实例:每次
new都会创建一个新的匿名子类对象(但类定义只编译生成一次.class文件)。 - 引用持有:可以赋值给变量,也可以直接作为方法参数传递(一次性使用)。
- 生命周期:与普通对象相同,由 GC 管理。如果匿名类对象持有外部类引用(非静态匿名类),可能阻止外部类实例被回收。
5️⃣ 匿名类对象的特殊行为
- this 指向匿名类对象:在匿名类内部,
this表示当前匿名子类的实例。 - 外部类.this:如果需要引用外部类对象,使用
外部类名.this。 - 局部变量捕获:只能访问 final 或 effectively final 的局部变量(Java 8+),这些变量会被复制到匿名类对象中(类似闭包)。
class Outer {
void method() {
int x = 10; // effectively final
Runnable r = new Runnable() {
public void run() {
System.out.println(x); // 合法,x 被捕获到匿名对象中
// x = 20; // 编译错误
}
};
}
}6️⃣ 匿名类 vs 普通子类对象对比
| 对比项 | 匿名类对象 | 普通子类对象 |
|---|---|---|
| 类名 | 无(编译器生成 $1) | 有显式名称 |
| 复用性 | 无法复用此类定义 | 可多次实例化 |
| 构造器 | 无(可用实例初始化块) | 有构造器 |
| 定义位置 | 表达式内部 | 独立文件或类内部 |
| 使用场景 | 一次性实现接口/继承类 | 需要反复使用的类 |
7️⃣ 示例:当作参数传递的子类对象
// 方法签名:需要 Runnable 子类对象
public void execute(Runnable task) { task.run(); }
// 调用时直接创建匿名子类对象
execute(new Runnable() {
@Override
public void run() {
System.out.println("任务执行");
}
});这里匿名类对象在 execute 方法内部被使用,方法结束后如果没有其他引用,对象即成为 GC 候选,体现了一次性子类的特点。
topic: 编程 tags:
- Java
- 概念