本文是 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 的各种特性,不断提升自己的编程能力。

更多推荐