Java 面向对象 · 交互课程

异常处理与内部类

异常处理与内部类

目标:理解 Java「把错误建模为类」的异常体系;会定义自定义异常并用 throw/throws 抛出、用 try-catch-finally 捕获(含多异常 catch 顺序);理解内部类「直接访问外围类成员(含 private)」的特性与匿名类的适用场景。

一、异常类层次

Java 中所有错误都对应一个类,根是 Throwable:

类说明
Throwable一切错误的根
Error严重故障(如 OutOfMemoryError),通常不可恢复,不建议捕获
Exception可处理的异常
└ 受检异常如 IOException:必须捕获或 throws 声明,否则编译报错
└ RuntimeException运行时异常(如 ArrayIndexOutOfBoundsException):不强制处理

二、抛出与捕获

自定义异常:以 Exception 为父类,用构造方法传递错误信息:

class PasswordException extends Exception {
    public PasswordException(String msg) { super(msg); }
}

throw / throws:throw new ... 显式抛出;方法签名用 throws 声明可能抛出的异常类:

void verify(String pwd) throws PasswordException {
    if (!pwd.equals(saved)) throw new PasswordException("密码错误");
}

try-catch-finally:

交互练习:catch 匹配(CatchMatcher)

try 块里抛出一个异常,JVM 按书写顺序逐个检查 catch 分支——第一个「抛出类是它本身或其子类」的分支接住; 全都接不住,异常继续向上抛。点选你认为会命中的分支。

try {
    open("report.txt");   // ← 抛出 FileNotFoundException
} catch (IOException e) { ... }
  catch (SQLException e) { ... }

抛出 new FileNotFoundException(),哪个分支接住?

顺带记住:finally 无论命中哪个分支(甚至没命中)都会执行——常用于释放资源。

"模拟密码锁"贯穿案例

文件保存密码 → 读写必然捕获 IOException;多次输入错误则抛出自定义 PasswordException 并锁定。一个场景串起「IO 异常 + 自定义异常 + 重试逻辑」。

交互实验:异常传播链(ExceptionFlowDiagram)

选场景后逐步执行:看异常从抛出点沿调用栈向上冒泡,直到被匹配的 catch 捕获(或无人捕获而终止)。

void verify(String pwd) throws PasswordException {
    if (!pwd.equals(saved)) throw new PasswordException("密码错误");
}

public static void main(String[] args) {
    try {
        verify("abc");          // 抛出点
    } catch (PasswordException e) {
        System.out.println("锁定:" + e.getMessage());
    } finally {
        System.out.println("清理资源");
    }
}

调用栈(顶帧在上)

verify密码不匹配 → throw new PasswordException
main进入 try 块,调用 verify

点「▶ 下一步」抛出异常。

三、内部类与匿名类

内部类:定义在另一个类内部的类,可直接访问外围类的所有成员(包括 private)——免传参、代码紧凑;代价是与外围类耦合更紧,且依赖外围类对象存在。编译后生成 Outer$Inner.class。

匿名类:没有名字、创建对象的同时定义类体,适合「只使用一次」的监听者/回调:

button.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) { /* ... */ }
});

交互实验:内部类结构图(InnerClassExplorer)

ShapeApp编译产物:ShapeApp$Listener.class
外围类成员
  • private canvas
  • private shapeCount
  • public draw()
Listener(内部类)

可直接访问外围类所有成员——包括 private 的。

class ShapeApp extends Frame {
    private Canvas canvas;

    class Listener implements ActionListener {
        public void actionPerformed(ActionEvent e) {
            draw();              // 直接访问外围类的成员
            System.out.println(canvas);  // 连 private 也能访问
        }
    }
}

优点:直接访问外围类所有成员(含 private),免传参、代码紧凑

缺点:内部类对象依赖外围类对象存在;与外围类耦合更紧

button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { ... } });——没有名字、创建对象的同时定义类体,适合「只使用一次」的监听者/回调。

思考题

  1. 受检异常与运行时异常在使用约束上有何不同?各举 1 例。
  2. 为什么多异常 catch 要把子类放前面?把 Exception 放第一个会怎样?
  3. 内部类实现监听者相比普通类的主要优势是什么?匿名类适合什么场景?

学习小测验

学习小测验

Q1

Java 中用于抛出异常的关键字是( )

Q2

内部类是指定义在( )中的类

Q3

捕获异常使用的代码块是( )

Q4

多异常 catch 中,子类与父类的顺序是( )

Q5

关于内部类,正确的是( )

Q6

匿名类适合什么场景?