异常处理与内部类
异常处理与内部类
目标:理解 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:
finally无论是否发生异常都会执行(常用于释放资源);- 多异常 catch 顺序:子类在前、父类在后——若把父类(如
IOException)放第一个,后面的子类分支不可达,编译报错。
交互练习: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("清理资源");
}
}调用栈(顶帧在上)
点「▶ 下一步」抛出异常。
三、内部类与匿名类
内部类:定义在另一个类内部的类,可直接访问外围类的所有成员(包括 private)——免传参、代码紧凑;代价是与外围类耦合更紧,且依赖外围类对象存在。编译后生成 Outer$Inner.class。
匿名类:没有名字、创建对象的同时定义类体,适合「只使用一次」的监听者/回调:
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) { /* ... */ }
});
交互实验:内部类结构图(InnerClassExplorer)
- private canvas
- private shapeCount
- public draw()
可直接访问外围类所有成员——包括 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 例。
- 为什么多异常 catch 要把子类放前面?把
Exception放第一个会怎样? - 内部类实现监听者相比普通类的主要优势是什么?匿名类适合什么场景?
学习小测验
学习小测验
Java 中用于抛出异常的关键字是( )
内部类是指定义在( )中的类
捕获异常使用的代码块是( )
多异常 catch 中,子类与父类的顺序是( )
关于内部类,正确的是( )
匿名类适合什么场景?