Java 匿名内部类
什么是匿名内部类?
匿名内部类是Java中一种特殊的内部类,它没有明确的类名。这种类在声明的同时就完成了实例化,因此只能使用一次。匿名内部类通常用于实现接口或扩展类,尤其是当这些实现或扩展仅在程序中使用一次时。
匿名内部类的名称由编译器自动生成,通常是外部类名加上一个美元符号和数字,如OuterClass$1
。
匿名内部类的语法
匿名内部类的基本语法如下:
// 继承类的匿名内部类
new 父类名() {
// 类体部分,可以重写方法
};
// 实现接口的匿名内部类
new 接口名() {
// 接口实现部分
};
匿名内部类的基本用法
示例1:实现接口
下面是一个实现Runnable
接口的匿名内部类示例:
public class AnonymousInnerClassExample {
public static void main(String[] args) {
// 使用匿名内部类实现Runnable接口
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("这是通过匿名内部类实现的Runnable接口");
}
};
// 使用这个匿名内部类实例
Thread thread = new Thread(runnable);
thread.start();
}
}
输出:
这是通过匿名内部类实现的Runnable接口
示例2:继承抽象类
abstract class Animal {
abstract void makeSound();
}
public class AnonymousInnerClassDemo {
public static void main(String[] args) {
// 使用匿名内部类继承抽象类
Animal dog = new Animal() {
@Override
void makeSound() {
System.out.println("汪汪汪!");
}
};
dog.makeSound();
// 另一个匿名内部类实例
Animal cat = new Animal() {
@Override
void makeSound() {
System.out.println("喵喵喵!");
}
};
cat.makeSound();
}
}
输出:
汪汪汪!
喵喵喵!
匿名内部类的特点
1. 只能使用一次
匿名内部类在创建后只能使用一次,无法重复使用。
2. 可以访问外部类的成员
与其他内部类一样,匿名内部类可以访问外部类的所有成员(包括私有成员)。
public class OuterClass {
private int num = 10;
public void display() {
// 创建接口的匿名实现类
InterfaceExample example = new InterfaceExample() {
@Override
public void show() {
// 访问外部类的私有变量
System.out.println("外部类的num值为: " + num);
}
};
example.show();
}
public static void main(String[] args) {
OuterClass outer = new OuterClass();
outer.display();
}
}
interface InterfaceExample {
void show();
}
输出:
外部类的num值为: 10
3. 访问局部变量的限制
匿名内部类可以访问所在方法的局部变量,但这些变量必须是final或实际上是final的(Java 8+)。
public class LocalVariableAccess {
public void greet() {
// 实际上是final的变量
String message = "Hello";
Greeting greeting = new Greeting() {
@Override
public void sayHello() {
// 访问局部变量message
System.out.println(message + " World!");
// 下面的代码会导致编译错误
// message = "Hi"; // 无法修改,因为实际上是final的
}
};
greeting.sayHello();
}
public static void main(String[] args) {
LocalVariableAccess obj = new LocalVariableAccess();
obj.greet();
}
}
interface Greeting {
void sayHello();
}
输出:
Hello World!
在Java 8之前,如果匿名内部类访问局部变量,需要明确将该变量声明为final。 Java 8引入了"effectively final"的概念,如果一个变量在初始化后没有被修改,它就被视为effectively final,可以被匿名内部类访问。
匿名内部类的实际应用场景
1. 事件处理
Java中的GUI编程(如Swing)中,匿名内部类广泛用于事件处理:
import javax.swing.*;
import java.awt.event.*;
public class ButtonClickDemo {
public static void main(String[] args) {
JFrame frame = new JFrame("按钮点击示例");
JButton button = new JButton("点击我");
// 使用匿名内部类处理按钮点击事件
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(frame, "按钮被点击了!");
}
});
frame.add(button);
frame.setSize(300, 200);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
2. 线程创建
创建和启动线程的简便方法:
public class ThreadExample {
public static void main(String[] args) {
// 使用匿名内部类创建线程
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for(int i = 1; i <= 5; i++) {
System.out.println("线程执行: 计数 " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
thread.start();
System.out.println("主线程继续执行...");
}
}
输出:
主线程继续执行...
线程执行: 计数 1
线程执行: 计数 2
线程执行: 计数 3
线程执行: 计数 4
线程执行: 计数 5
3. 比较器实现
在集合排序时使用匿名内部类实现比较器:
import java.util.*;
public class ComparatorExample {
public static void main(String[] args) {
List<Person> personList = new ArrayList<>();
personList.add(new Person("张三", 25));
personList.add(new Person("李四", 30));
personList.add(new Person("王五", 20));
// 使用匿名内部类实现Comparator接口
Collections.sort(personList, new Comparator<Person>() {
@Override
public int compare(Person p1, Person p2) {
return p1.getAge() - p2.getAge(); // 按年龄升序排序
}
});
// 打印排序后的列表
for(Person person : personList) {
System.out.println(person);
}
}
}
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}
输出:
Person{name='王五', age=20}
Person{name='张三', age=25}
Person{name='李四', age=30}
匿名内部类与Lambda表达式
Java 8引入的Lambda表达式可以在很多情况下替代匿名内部类,使代码更简洁。当匿名内部类实现的是只有一个抽象方法的接口(函数式接口)时,可以使用Lambda表达式。
比较匿名内部类和Lambda表达式
import java.util.*;
public class LambdaVsAnonymous {
public static void main(String[] args) {
List<String> names = Arrays.asList("Tom", "Alice", "Bob", "John");
// 使用匿名内部类
System.out.println("使用匿名内部类排序:");
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s1.length() - s2.length();
}
});
System.out.println(names);
// 重新排序
names = Arrays.asList("Tom", "Alice", "Bob", "John");
// 使用Lambda表达式
System.out.println("使用Lambda表达式排序:");
Collections.sort(names, (s1, s2) -> s1.length() - s2.length());
System.out.println(names);
}
}
输出:
使用匿名内部类排序:
[Tom, Bob, John, Alice]
使用Lambda表达式排序:
[Tom, Bob, John, Alice]
虽然Lambda表达式可以替代匿名内部类,但两者有一些细微的区别:
- this引用:在匿名内部类中,this指向匿名内部类实例;而在Lambda表达式中,this指向包含Lambda的外部类实例
- 编译方式:匿名内部类编译为独立的类文件,Lambda表达式则使用invokedynamic指令
匿名内部类的局限性
- 不能有构造函数:匿名内部类没有名称,所以不能有显式的构造函数
- 不能同时继承类和实现接口:只能选择一种
- 不能是静态的:匿名内部类总是与特定实例相关联
- 可读性问题:大型匿名内部类可能影响代码可读性
匿名内部类的内存考虑
匿名内部类会持有外部类的引用,可能导致内存泄漏,特别是在长时间运行的应用中。如果匿名内部类的生命周期比外部类长,应当注意避免不必要的引用。
总结
匿名内部类是Java中一个强大的特性,它允许我们在需要临时使用一个接口或类的实现时,直接在代码中就地声明和实例化。主要特点包括:
- 没有明确的类名,声明的同时就被实例化
- 可以实现接口或继承类,但不能两者兼具
- 能够访问外部类的所有成员,包括私有成员
- 可以访问局部变量,但这些变量必须是final或effectively final
- 常用于事件处理、线程创建、比较器等场景
- 在Java 8后,对于函数式接口可以被Lambda表达式替代
匿名内部类为Java的面向对象编程提供了更大的灵活性,尤其适合于只需使用一次的类实现。
练习
为了巩固对匿名内部类的理解,请尝试以下练习:
- 创建一个自定义接口
Calculator
,包含一个calculate(int a, int b)
方法,并使用匿名内部类实现加、减、乘、除四种不同的计算器 - 使用匿名内部类实现一个文件过滤器,过滤出目录中所有的
.java
文件 - 创建一个带有定时功能的程序,使用匿名内部类实现
TimerTask
接口,每隔1秒打印当前时间
扩展阅读
- Java官方文档中关于内部类的部分
- 深入理解匿名内部类与Lambda表达式的区别
- 设计模式中匿名内部类的应用,如观察者模式、策略模式等
通过本章的学习,你应当能够理解匿名内部类的概念、语法和使用场景,能够在实际编程中正确应用匿名内部类来简化代码结构。随着对Java的深入学习,你会发现匿名内部类是一个非常实用的编程工具。