【Java 课程作业】匿名内部类核心应用场景详解(含方法执行时间计算实战)
本文是 Java 面向对象程序设计课程的课后作业,重点讲解匿名内部类最典型的应用场景——通用方法执行时间计算,同时扩展介绍线程创建、排序比较、事件处理等其他常用场景。通过传统写法与匿名内部类写法的对比,深入理解匿名内部类的设计思想与优势。
文章目录
一、什么是匿名内部类?
匿名内部类是 Java 中一种没有名字的内部类,它的本质是继承了某个父类或实现了某个接口的子类对象。它的主要作用是快速创建一个只使用一次的类实例,避免单独定义一个完整的类。
1.1 基本语法
// 实现接口的匿名内部类
父类/接口名 对象名 = new 父类/接口名() {
// 重写父类或接口的方法
@Override
public void method() {
// 方法实现
}
};
1.2 核心特点
- 没有类名,只能创建一个实例
- 必须继承一个父类或实现一个接口
- 不能定义构造方法(因为没有名字)
- 可以访问外部类的成员变量和方法
- 适合创建只使用一次的简单类
二、核心应用场景:计算方法执行时间(作业重点)
这是本次作业的核心内容,也是匿名内部类最经典的应用场景之一。我们将实现一个通用的计时工具,可以计算任意方法的执行时间。
2.1 问题引入
在开发中,我们经常需要测试某个方法的执行性能。传统的写法是这样的:
// 测试方法 A 的执行时间
long start = System.currentTimeMillis();
methodA();
long end = System.currentTimeMillis();
System.out.println("方法 A 执行时间:" + (end - start) + "毫秒");
// 测试方法 B 的执行时间
long start2 = System.currentTimeMillis();
methodB();
long end2 = System.currentTimeMillis();
System.out.println("方法 B 执行时间:" + (end2 - start2) + "毫秒");
这种写法存在明显的问题:
- 代码重复严重:每个方法都要写一遍相同的计时逻辑
- 代码冗余:业务逻辑和计时逻辑混杂在一起
- 难以维护:如果要修改计时单位(比如改成秒),需要修改所有地方
2.2 匿名内部类解决方案
我们可以利用 Runnable 接口和匿名内部类,实现一个通用的计时工具:
/**
* 通用计时工具类
*/
public class TimeUtils {
/**
* 计算方法执行时间(毫秒)
* @param task 要执行的任务
* @return 执行时间(毫秒)
*/
public static long calculateTime(Runnable task) {
long startTime = System.currentTimeMillis();
task.run(); // 执行传入的任务
long endTime = System.currentTimeMillis();
return endTime - startTime;
}
/**
* 计算方法执行时间(秒)
* @param task 要执行的任务
* @return 执行时间(秒,保留 3 位小数)
*/
public static double calculateTimeInSeconds(Runnable task) {
long ms = calculateTime(task);
return ms / 1000.0;
}
}
2.3 使用示例
现在我们可以用这个工具来计算任意方法的执行时间,只需要将方法逻辑写在匿名内部类的 run() 方法中即可。
public class TimeTest {
// 示例方法 1:计算 1 到 n 的和
public static long sum(int n) {
long result = 0;
for (int i = 1; i <= n; i++) {
result += i;
}
return result;
}
// 示例方法 2:生成 100 万以内的所有素数(使用之前学的埃拉托斯特尼筛法)
public static int countPrimes(int max) {
if (max < 2) return 0;
boolean[] isPrime = new boolean[max + 1];
java.util.Arrays.fill(isPrime, true);
isPrime[0] = isPrime[1] = false;
for (int i = 2; i <= Math.sqrt(max); i++) {
if (isPrime[i]) {
for (int j = i * i; j <= max; j += i) {
isPrime[j] = false;
}
}
}
int count = 0;
for (boolean b : isPrime) {
if (b) count++;
}
return count;
}
public static void main(String[] args) {
// 计算 sum 方法执行时间
long time1 = TimeUtils.calculateTime(new Runnable() {
@Override
public void run() {
sum(100000000);
}
});
System.out.println("sum 方法执行时间:" + time1 + " 毫秒");
// 计算 countPrimes 方法执行时间(秒)
double time2 = TimeUtils.calculateTimeInSeconds(new Runnable() {
@Override
public void run() {
int count = countPrimes(1000000);
System.out.println("100 万以内的素数个数:" + count);
}
});
System.out.println("countPrimes 方法执行时间:" + String.format("%.3f", time2) + " 秒");
}
}
2.4 运行结果
sum 方法执行时间:42 毫秒
100 万以内的素数个数:78498
countPrimes 方法执行时间:0.012 秒
2.5 优点总结
- 代码复用:计时逻辑只写了一次,可以重复使用
- 解耦:业务逻辑和计时逻辑完全分离
- 简洁优雅:不需要为每个方法单独写计时代码
- 灵活通用:可以计算任何无参数、无返回值的方法
三、匿名内部类的其他常用场景
除了计算方法执行时间,匿名内部类在 Java 开发中还有很多非常常用的场景。
3.1 场景一:创建线程
这是匿名内部类最常见的用法之一,用于快速创建一个线程。
传统写法(单独定义 Thread 子类)
class MyThread extends Thread {
@Override
public void run() {
System.out.println("线程执行中...");
}
}
public class ThreadTest {
public static void main(String[] args) {
Thread thread = new MyThread();
thread.start();
}
}
匿名内部类写法
public class ThreadTest {
public static void main(String[] args) {
// 继承 Thread 类的匿名内部类
new Thread() {
@Override
public void run() {
System.out.println("线程 1 执行中...");
}
}.start();
// 实现 Runnable 接口的匿名内部类
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程 2 执行中...");
}
}).start();
}
}
3.2 场景二:集合排序(Comparator 接口)
在对集合进行排序时,我们经常需要自定义比较规则,匿名内部类是实现 Comparator 接口的最佳方式。
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class SortTest {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("apple");
list.add("banana");
list.add("cherry");
list.add("date");
// 按字符串长度降序排序
Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s2.length() - s1.length();
}
});
System.out.println("按长度降序排序:" + list);
}
}
3.3 场景三:图形界面事件处理
在 Swing 或 JavaFX 等图形界面开发中,匿名内部类被广泛用于处理按钮点击、鼠标移动等事件。
import javax.swing.JButton;
import javax.swing.JFrame;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class ButtonTest {
public static void main(String[] args) {
JFrame frame = new JFrame("匿名内部类事件处理");
JButton button = new JButton("点击我");
// 为按钮添加点击事件监听器
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("按钮被点击了!");
}
});
frame.add(button);
frame.setSize(300, 200);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
四、匿名内部类的优缺点与注意事项
4.1 优点
- 简洁高效:不需要单独定义一个类,减少代码量
- 方便快捷:可以在使用的地方直接创建实例
- 封装性好:将类的定义和实例化合并在一起
- 适合一次性使用:对于只使用一次的类,匿名内部类是最佳选择
4.2 缺点
- 可读性较差:对于复杂的逻辑,匿名内部类会让代码变得难以阅读
- 不能复用:只能创建一个实例,无法在其他地方使用
- 不能定义构造方法:无法进行复杂的初始化操作
- 不能有静态成员:匿名内部类中不能定义静态变量和静态方法
4.3 注意事项
- 匿名内部类可以访问外部类的成员变量,但如果要访问方法中的局部变量,该变量必须是
final的(Java 8 及以上版本可以不加final,但实际上仍然是隐式 final 的) - 匿名内部类不能是抽象的,必须实现所有抽象方法
- 匿名内部类的实例只能使用一次,如果需要多次使用,应该定义一个普通的类
五、学习总结
通过这次作业,我对匿名内部类的应用有了深入的理解。以前我总是觉得匿名内部类很抽象,不知道什么时候该用它,现在终于明白了它的设计初衷——为了快速创建一个只使用一次的类实例,避免代码冗余。
这次作业中实现的通用计时工具让我印象最深刻。以前测试方法性能的时候,我总是会写很多重复的计时代码,现在用匿名内部类实现了一个通用的工具类,只需要一行代码就能计算任何方法的执行时间,非常方便。这让我深刻体会到了面向对象编程中"封装"和"复用"的思想。
当然,匿名内部类也不是万能的。对于复杂的逻辑或者需要多次使用的类,我们还是应该定义一个普通的类。只有在合适的场景下使用匿名内部类,才能让我们的代码更加简洁优雅。
作为 Java 学习者,我发现很多看似复杂的语法特性,其实都是为了解决实际开发中的某个具体问题而设计的。只要我们理解了它要解决的问题,就能很自然地掌握它的用法。在未来的学习中,我会继续深入学习 Java 的各种特性,不断提升自己的编程能力。
更多推荐
所有评论(0)