匿名类(Anonymous Inner Class)是 Java 中一种特殊的内部类,它没有显式的类名 ,通常用于一次性实现某个接口或继承某个类,并在定义处立即创建实例。它是 Java 早期实现回调、事件监听等场景的常用手段,也是理解内部类机制的重要一环。
一、为什么叫"匿名"?
普通类有明确的类名,例如:
java
class MyTask implements Runnable {
@Override
public void run() {
System.out.println("执行任务");
}
}
使用时通过 new MyTask() 创建对象。而匿名类省略了 class MyTask 这一命名步骤,直接把类的实现体写在 new 后面:
java
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println("执行任务");
}
};
这里没有给这个类起名字,所以称为"匿名类"。编译器在编译时会自动为它生成一个类似 外部类$1.class 的字节码文件,但源码中不可见。
二、基本语法
java
new 父类型(参数) {
// 类体:重写方法、定义字段等
};
其中"父类型"可以是:
- 一个接口
- 一个抽象类
- 一个具体类
匿名类必须恰好选择其中一种,不能同时继承类和实现接口。它是一次性使用的,定义的同时会创建对象,末尾需要分号。
三、严格分类与示例
根据父类型的性质,匿名类可以严格分为以下三类,互不重叠。
3.1 实现接口的匿名类
父类型是接口,匿名类必须实现接口中的所有抽象方法。
java
interface Greeting {
void sayHello();
}
public class Main {
public static void main(String[] args) {
Greeting g = new Greeting() {
@Override
public void sayHello() {
System.out.println("Hello from anonymous class!");
}
};
g.sayHello(); // 输出:Hello from anonymous class!
}
}
典型场景 :事件监听器、Runnable、Comparator 等函数式接口(Java 8 后常被 Lambda 替代)。
3.2 继承抽象类的匿名类
父类型是抽象类,匿名类必须实现所有抽象方法。抽象类中已有的具体方法可以直接继承使用。
java
abstract class Shape {
abstract double area();
void print() {
System.out.println("面积:" + area());
}
}
public class Main {
public static void main(String[] args) {
Shape circle = new Shape() {
double r = 5.0; // 可以添加自己的字段
@Override
double area() {
return Math.PI * r * r;
}
};
circle.print(); // 输出:面积:78.53981633974483
}
}
典型场景:需要复用抽象类中的部分实现,同时补充抽象方法。
3.3 继承具体类的匿名类
父类型是一个普通的具体类,匿名类可以选择性地重写父类方法,也可以添加自己的成员。
根据父类构造器的调用方式,又可分为两种子情况。
3.3.1 调用无参构造器
父类有无参构造器时,直接写 new 父类() { ... }。
java
class Animal {
void sound() {
System.out.println("动物发出声音");
}
}
public class Main {
public static void main(String[] args) {
Animal cat = new Animal() {
@Override
void sound() {
System.out.println("喵喵");
}
};
cat.sound(); // 输出:喵喵
}
}
3.3.2 调用有参构造器
父类只有带参构造器时,必须在 new 时传入相应参数。
java
class Person {
String name;
Person(String name) {
this.name = name;
}
void say() {
System.out.println("我是 " + name);
}
}
public class Main {
public static void main(String[] args) {
Person p = new Person("张三") {
@Override
void say() {
System.out.println("匿名子类说:我是 " + name);
}
};
p.say(); // 输出:匿名子类说:我是 张三
}
}
典型场景 :继承 Thread 类并重写 run(),同时调用 Thread(String name) 构造器。
java
Thread t = new Thread("worker") {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 正在运行");
}
};
t.start(); // 输出:worker 正在运行
四、通用特点与限制
无论哪种分类,匿名类都具有以下共同特点:
- 可以访问外部类的成员,包括私有成员。
- 可以访问方法中的局部变量 ,但该变量必须是
final或"实际上的 final"(effectively final)。 - 不能定义静态成员 (除非是
static final常量)。 - 不能有显式构造器,但可以通过实例初始化块完成初始化。
- 隐式持有外部类引用,若生命周期过长可能导致内存泄漏。
- 没有类名,因此无法在其他地方复用,也无法被继承。
五、与 Lambda 表达式的区别
Java 8 引入 Lambda 后,部分匿名类可以被更简洁的 Lambda 替代,但二者有本质区别:
| 特性 | 匿名类 | Lambda |
|---|---|---|
| 适用父类型 | 接口、抽象类、具体类 | 仅函数式接口(单抽象方法) |
| 语法 | 较冗长 | 简洁 |
this 指向 |
匿名类自身 | 外部类 |
| 能否添加字段/方法 | 可以 | 不能 |
| 字节码 | 生成独立 .class 文件 |
通常使用 invokedynamic |
因此,当接口包含多个抽象方法,或需要继承抽象类/具体类,或需要在实现中增加额外字段、方法时,匿名类仍是唯一选择。
六、总结
匿名类是一种"定义即使用"的临时类,适合逻辑简单、只使用一次的场景。根据父类型,它严格分为实现接口 、继承抽象类 、继承具体类三类,其中继承具体类又可按构造器调用方式细分。匿名类让代码更紧凑,但也可能降低可读性。在现代 Java 开发中,函数式接口优先使用 Lambda;而对于多方法接口、抽象类或具体类的扩展,匿名类依然不可或缺。