跳到主要内容

Java 匿名内部类

什么是匿名内部类?

匿名内部类是Java中一种特殊的内部类,它没有明确的类名。这种类在声明的同时就完成了实例化,因此只能使用一次。匿名内部类通常用于实现接口或扩展类,尤其是当这些实现或扩展仅在程序中使用一次时。

提示

匿名内部类的名称由编译器自动生成,通常是外部类名加上一个美元符号和数字,如OuterClass$1

匿名内部类的语法

匿名内部类的基本语法如下:

java
// 继承类的匿名内部类
new 父类名() {
// 类体部分,可以重写方法
};

// 实现接口的匿名内部类
new 接口名() {
// 接口实现部分
};

匿名内部类的基本用法

示例1:实现接口

下面是一个实现Runnable接口的匿名内部类示例:

java
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:继承抽象类

java
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. 可以访问外部类的成员

与其他内部类一样,匿名内部类可以访问外部类的所有成员(包括私有成员)。

java
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+)。

java
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)中,匿名内部类广泛用于事件处理:

java
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. 线程创建

创建和启动线程的简便方法:

java
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. 比较器实现

在集合排序时使用匿名内部类实现比较器:

java
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表达式

java
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表达式可以替代匿名内部类,但两者有一些细微的区别:

  1. this引用:在匿名内部类中,this指向匿名内部类实例;而在Lambda表达式中,this指向包含Lambda的外部类实例
  2. 编译方式:匿名内部类编译为独立的类文件,Lambda表达式则使用invokedynamic指令

匿名内部类的局限性

  1. 不能有构造函数:匿名内部类没有名称,所以不能有显式的构造函数
  2. 不能同时继承类和实现接口:只能选择一种
  3. 不能是静态的:匿名内部类总是与特定实例相关联
  4. 可读性问题:大型匿名内部类可能影响代码可读性

匿名内部类的内存考虑

匿名内部类会持有外部类的引用,可能导致内存泄漏,特别是在长时间运行的应用中。如果匿名内部类的生命周期比外部类长,应当注意避免不必要的引用。

总结

匿名内部类是Java中一个强大的特性,它允许我们在需要临时使用一个接口或类的实现时,直接在代码中就地声明和实例化。主要特点包括:

  1. 没有明确的类名,声明的同时就被实例化
  2. 可以实现接口或继承类,但不能两者兼具
  3. 能够访问外部类的所有成员,包括私有成员
  4. 可以访问局部变量,但这些变量必须是final或effectively final
  5. 常用于事件处理、线程创建、比较器等场景
  6. 在Java 8后,对于函数式接口可以被Lambda表达式替代

匿名内部类为Java的面向对象编程提供了更大的灵活性,尤其适合于只需使用一次的类实现。

练习

为了巩固对匿名内部类的理解,请尝试以下练习:

  1. 创建一个自定义接口Calculator,包含一个calculate(int a, int b)方法,并使用匿名内部类实现加、减、乘、除四种不同的计算器
  2. 使用匿名内部类实现一个文件过滤器,过滤出目录中所有的.java文件
  3. 创建一个带有定时功能的程序,使用匿名内部类实现TimerTask接口,每隔1秒打印当前时间

扩展阅读

  • Java官方文档中关于内部类的部分
  • 深入理解匿名内部类与Lambda表达式的区别
  • 设计模式中匿名内部类的应用,如观察者模式、策略模式等

通过本章的学习,你应当能够理解匿名内部类的概念、语法和使用场景,能够在实际编程中正确应用匿名内部类来简化代码结构。随着对Java的深入学习,你会发现匿名内部类是一个非常实用的编程工具。