目录
实训概述
实训目标
知识预备
实训任务与分步实现
代码完整示例
运行效果与问题分析
实训总结与心得体会
拓展思考
1. 实训概述
本次实训围绕 Java 异常处理机制 展开,覆盖异常基础语法、异常捕获、异常抛出、自定义异常等核心知识点。结合模拟业务场景完成代码编写、调试与异常处理,帮助理解 Java 异常体系的设计思想,掌握实际开发中异常的规范使用方式。
异常处理是保障程序健壮性的关键,合理处理异常可以避免程序意外终止,同时精准定位运行错误。本实训从基础用法过渡到项目常用的自定义异常,循序渐进完成练习,适合 Java 入门阶段巩固知识点。
2. 实训目标
掌握 Java 异常分类,区分 Error、编译时异常、运行时异常。
熟练使用 try-catch-finally、throws、throw 完成异常捕获与抛出。
理解 finally 代码块的执行特性与使用场景。
能够独立编写自定义异常类,并结合业务场景使用。
学会分析异常堆栈信息,排查程序运行错误。
养成规范的异常编码习惯,避免空捕获、异常隐藏等问题。
3. 知识预备
3.1 Java 异常体系
Throwable 是所有异常和错误的顶层父类,分为两大分支:
Error:系统级严重错误,如内存溢出、JVM 崩溃,程序无法捕获处理。
Exception:程序可处理异常,分为两类:
受检异常(编译时异常):直接继承 Exception,编译强制要求捕获或抛出,如 IOException。
运行时异常:继承 RuntimeException,编译不强制处理,如空指针、数组越界、参数非法。
3.2 核心关键字
try:包裹可能出现异常的代码段。
catch:捕获并处理对应类型的异常,可多分支捕获不同异常。
finally:无论是否发生异常,代码一定会执行,常用于释放资源。
throw:手动在方法内部抛出单个异常对象。
throws:声明方法可能抛出的异常,写在方法签名处。
3.3 自定义异常
业务场景下内置异常语义不足时,可自定义异常:
继承 RuntimeException:自定义运行时异常(项目主流用法)。
继承 Exception:自定义编译时异常,强制调用者处理。
4. 实训任务与分步实现
本次实训分为基础异常处理、finally 测试、手动抛出异常、自定义异常实战四个模块,依次完成练习。
任务一:基础异常捕获(数组下标越界、算术异常)
需求
编写代码模拟除法运算与数组访问,对算术异常、数组下标越界异常进行捕获处理,保证程序不会崩溃。
实现思路
使用 try 包裹运算、数组访问代码。
编写多个 catch 分支,分别捕获不同类型异常。
在捕获分支中输出异常信息与提示文案。
任务二:finally 代码块使用测试
需求
验证 finally 执行规则:无论正常运行、抛出异常、代码 return,finally 代码块都会执行。模拟资源释放场景。
任务三:使用 throw + throws 主动抛出异常
需求
编写数值校验方法,当传入负数时,使用 throw 手动抛出内置运行时异常;方法通过 throws 声明异常,由调用方统一捕获处理。
任务四:自定义异常综合实战(核心任务)
需求
模拟用户账户余额提现场景:
自定义业务异常 BalanceException,用于表示余额不足异常。
编写提现方法,当提现金额大于账户余额、金额为负数时,主动抛出自定义异常。
调用方法并捕获自定义异常,输出友好提示信息。
5. 代码完整示例
5.1 任务一:多异常捕获
/**
 * 基础异常捕获:算术异常、数组越界异常
 */
public class ExceptionTest1 {
    public static void main(String[] args) {
        int[] arr = {10, 20, 30};
        try {
            // 算术运算:除数为0
            int num = 10 / 0;
            // 数组访问:下标越界
            System.out.println(arr[5]);
        } catch (ArithmeticException e) {
            System.out.println("捕获算术异常:除数不能为0");
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("捕获数组异常:数组下标超出范围");
        } catch (Exception e) {
            // 父类异常放在最后,捕获所有未知异常
            System.out.println("程序出现未知异常");
        }
        System.out.println("程序继续运行...");
    }
}
5.2 任务二:finally 代码块测试
/**
 * finally 代码块演示
 */
public class ExceptionTest2 {
    public static void main(String[] args) {
        try {
            System.out.println("正常代码执行");
            int a = 5 / 0;
            return;
        } catch (Exception e) {
            System.out.println("捕获到异常");
        } finally {
            // 无论是否异常、是否return,都会执行
            System.out.println("finally 代码块:执行资源释放操作");
        }
    }
}
5.3 任务三:throw & throws 主动抛出异常
/**
 * throw + throws 手动抛出异常
 */
public class ExceptionTest3 {
    // 方法声明可能抛出的异常
    public static void checkNum(int num) throws IllegalArgumentException {
        if (num < 0) {
            // 手动抛出异常对象
            throw new IllegalArgumentException("数值不能为负数");
        }
        System.out.println("数值校验通过:" + num);
    }

    public static void main(String[] args) {
        try {
            checkNum(-8);
        } catch (IllegalArgumentException e) {
            System.out.println("异常提示:" + e.getMessage());
        }
    }
}
5.4 任务四:自定义异常(余额不足业务场景)
步骤1:自定义业务异常类
/**
 * 自定义余额不足异常(运行时异常)
 */
public class BalanceException extends RuntimeException {
    // 无参构造
    public BalanceException() {
        super();
    }

    // 携带异常信息构造方法
    public BalanceException(String message) {
        super(message);
    }
}
步骤2:业务逻辑类
/**
 * 账户业务类:模拟提现功能
 */
public class Account {
    // 账户余额
    private double balance;

    public Account(double balance) {
        this.balance = balance;
    }

    /**
     * 提现方法
     * @param money 提现金额
     */
    public void withdraw(double money) {
        if (money < 0) {
            throw new BalanceException("提现金额不能为负数");
        }
        if (money > balance) {
            throw new BalanceException("余额不足,当前余额:" + balance);
        }
        // 扣减余额
        balance -= money;
        System.out.println("提现成功,剩余余额:" + balance);
    }

    public double getBalance() {
        return balance;
    }
}
步骤3:测试调用类
/**
 * 自定义异常测试主类
 */
public class ExceptionTest4 {
    public static void main(String[] args) {
        // 初始化账户,余额100
        Account account = new Account(100);
        try {
            // 模拟提现 200
            account.withdraw(200);
        } catch (BalanceException e) {
            // 捕获自定义异常
            System.out.println("业务异常:" + e.getMessage());
        }

        System.out.println("========================");

        try {
            // 模拟负数提现
            account.withdraw(-50);
        } catch (BalanceException e) {
            System.out.println("业务异常:" + e.getMessage());
        }
    }
}
6. 运行效果与问题分析
6.1 任务一运行结果
捕获算术异常:除数不能为0
程序继续运行...
分析:代码先触发除数为0的算术异常,进入对应 catch 分支,后续数组代码不会执行,程序正常结束。
6.2 任务二运行结果
正常代码执行
捕获到异常
finally 代码块:执行资源释放操作
分析:出现异常后依然执行 finally,常用于关闭文件流、数据库连接等资源。
6.3 任务三运行结果
异常提示:数值不能为负数
分析:方法内通过 throw 主动抛异常,方法签名用 throws 声明,最终由调用方捕获处理。
6.4 任务四运行结果
业务异常:余额不足,当前余额:100.0
========================
业务异常:提现金额不能为负数
分析:根据不同业务场景抛出同一种自定义异常,区分不同错误原因,语义清晰,便于业务处理。
常见问题排查
多个 catch 顺序错误:父类异常不能放在子类异常前面,否则子类捕获失效。
空 catch 块:只写 {} 不处理异常,会隐藏错误,实训和开发中禁止使用。
混淆 throw 和 throws:throw 在方法内部抛异常对象;throws 在方法上声明异常类型。
7. 实训总结与心得体会
7.1 知识点总结
Java 异常分为错误、编译时异常、运行时异常三大类,日常开发重点处理运行时异常。
try-catch-finally 是最基础的异常处理结构,finally 优先用于资源释放。
throw 用于手动抛出异常,throws 用于向外声明异常,实现异常向上传递。
自定义异常继承 RuntimeException 最为常用,可以根据业务场景定义专属异常,让错误信息更具业务含义。
异常处理的核心目的:防止程序终止、快速定位错误、统一错误提示。
7.2 实训心得
通过本次实训,我理解了异常并不是程序 bug,而是 Java 提供的一套错误处理机制。在代码编写过程中,不能一味回避异常,而是主动预判可能出现的问题并做处理。
内置异常只能描述通用错误,在模拟提现的业务场景中,自定义异常可以精准表达“余额不足”这类业务问题,让代码可读性大幅提升。同时在调试过程中,通过异常堆栈信息可以快速找到出错代码行,提升排错效率。
今后编码中会养成规范处理异常的习惯,不随意省略 catch 逻辑,合理使用 finally 释放资源,在复杂业务中使用自定义异常规范错误体系。
8. 拓展思考
编译时异常和运行时异常该如何选择?什么场景下必须使用编译时异常?
如果多层方法调用都抛出异常,异常会如何传递?
在 Web 项目中,如何结合自定义异常实现全局统一异常处理?
finally 中使用 return 会出现什么问题?为什么不建议在 finally 中写 return?

更多推荐