欢迎光临
我们一直在努力

Java异常终极试炼:4道经典练习题拆解,彻底搞懂try-catch-finally执行顺序!

各位Java小伙伴👨💻,是不是学完异常理论后,一遇到try-catch-finally的执行顺序、return覆盖、嵌套异常这些场景就懵圈?

别慌!今天咱们不聊枯燥的理论,直接上手4道从基础到高阶的异常练习题——从“银行转账自定义异常实战”到“嵌套try-catch异常传递”,拆解每一行代码的执行逻辑,把异常的核心坑点一网打尽!

一、实战篇:银行账户转账(自定义异常综合应用)

这是异常在实际开发中的典型场景,考验自定义异常、异常校验、try-catch捕获的综合能力,先看需求:

需求拆解

  • 定义BankAccount类:包含账户名、余额,以及构造方法、getter/setter;

  • 自定义3个异常:余额不足、金额非法、账户不存在;

  • 定义BankService类:实现转账方法,包含参数校验、转账逻辑、日志打印;

  • 测试4个场景:3种异常+1种成功,用try-catch捕获并打印日志。

  • 完整代码实现

    import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; // 1. 自定义异常:余额不足 class InsufficientBalanceException extends RuntimeException { public InsufficientBalanceException() { super(); } public InsufficientBalanceException(String msg) { super(msg); } } // 2. 自定义异常:金额非法 class InvalidAmountException extends RuntimeException { public InvalidAmountException() { super(); } public InvalidAmountException(String msg) { super(msg); } } // 3. 自定义异常:账户不存在 class AccountNotFoundException extends RuntimeException { public AccountNotFoundException() { super(); } public AccountNotFoundException(String msg) { super(msg); } } // 银行账户类 class BankAccount { private String accountName; // 账户名 private double balance; // 余额 // 构造方法 public BankAccount(String accountName, double balance) { this.accountName = accountName; this.balance = balance; } // getter/setter public String getAccountName() { return accountName; } public void setAccountName(String accountName) { this.accountName = accountName; } public double getBalance() { return balance; } public void setBalance(double balance) { this.balance = balance; } } // 银行服务类(核心转账逻辑) class BankService { // 转账方法:fromAccount(转出账户)、toAccount(转入账户)、amount(金额) public void transfer(BankAccount fromAccount, BankAccount toAccount, double amount) { String result = "失败"; // 转账结果默认失败 DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); String time = LocalDateTime.now().format(formatter); try { // 1. 校验转账金额:≤0 抛金额非法异常 if (amount <= 0) { throw new InvalidAmountException("转账金额[" + amount + "]非法,必须大于0"); } // 2. 校验目标账户:为空抛账户不存在异常 if (toAccount == null) { throw new AccountNotFoundException("目标账户不存在"); } // 3. 校验转出账户余额:不足抛余额不足异常 if (fromAccount.getBalance() < amount) { throw new InsufficientBalanceException("转出账户[" + fromAccount.getAccountName() + "]余额不足,当前余额:" + fromAccount.getBalance() + ",需转账:" + amount); } // 4. 校验通过,执行转账 fromAccount.setBalance(fromAccount.getBalance() – amount); // 转出账户减钱 toAccount.setBalance(toAccount.getBalance() + amount); // 转入账户加钱 result = "成功"; // 转账成功 } catch (InvalidAmountException | AccountNotFoundException | InsufficientBalanceException e) { // 捕获自定义异常,打印提示 System.out.println("【异常提示】" + e.getMessage()); } finally { // 5. 无论成功/失败,打印转账日志 String fromName = fromAccount != null ? fromAccount.getAccountName() : "未知账户"; String toName = toAccount != null ? toAccount.getAccountName() : "未知账户"; System.out.println("[" + time + "] 转账操作:从" + fromName + "到" + toName + ",金额" + amount + ",转账结果:" + result); } } } // 测试类 public class BankTransferTest { public static void main(String[] args) { // 初始化账户 BankAccount zhangsan = new BankAccount("张三", 1000.0); BankAccount lisi = new BankAccount("李四", 500.0); BankService bankService = new BankService(); System.out.println("===== 场景1:转账金额≤0(金额非法) ====="); bankService.transfer(zhangsan, lisi, -500.0); System.out.println("\\n===== 场景2:目标账户为空(账户不存在) ====="); bankService.transfer(zhangsan, null, 200.0); System.out.println("\\n===== 场景3:转出余额不足(余额不足) ====="); bankService.transfer(zhangsan, lisi, 2000.0); System.out.println("\\n===== 场景4:转账成功 ====="); bankService.transfer(zhangsan, lisi, 300.0); // 打印转账后余额 System.out.println("【转账后】张三余额:" + zhangsan.getBalance() + ",李四余额:" + lisi.getBalance()); } }

    运行结果(示例)

    ===== 场景1:转账金额≤0(金额非法) ===== 【异常提示】转账金额[-500.0]非法,必须大于0 [2026-01-14 15:30:00] 转账操作:从张三到李四,金额-500.0,转账结果:失败 ===== 场景2:目标账户为空(账户不存在) ===== 【异常提示】目标账户不存在 [2026-01-14 15:30:00] 转账操作:从张三到未知账户,金额200.0,转账结果:失败 ===== 场景3:转出余额不足(余额不足) ===== 【异常提示】转出账户[张三]余额不足,当前余额:1000.0,需转账:2000.0 [2026-01-14 15:30:00] 转账操作:从张三到李四,金额2000.0,转账结果:失败 ===== 场景4:转账成功 ===== [2026-01-14 15:30:00] 转账操作:从张三到李四,金额300.0,转账结果:成功 【转账后】张三余额:700.0,李四余额:800.0

    核心亮点

  • 自定义异常继承RuntimeException(运行时异常,无需强制声明),贴合业务场景;

  • finally块保证日志必打印,是实际开发中“资源释放、日志记录”的常用写法;

  • 多异常合并捕获(|),简化代码,符合Java 7+的语法规范。

  • 二、基础坑:finally含return,返回值被“强制覆盖”

    这是新手最容易踩的坑——finally里的return会覆盖try/catch里的返回值,先看代码:

    题目代码

    public class TryCatchFinallyExercise { public static void main(String[] args) { int result = calculateNumber(5, 0); System.out.println("最终返回值:" + result); } public static int calculateNumber(int a, int b) { int num = 0; try { // 尝试执行除法运算,b=0时会抛出算术异常 num = a / b; System.out.println("try块执行:num = " + num); } catch (ArithmeticException e) { // 捕获算术异常 num = -1; System.out.println("catch块执行:捕获到算术异常,num = " + num); } finally { // finally块无论是否发生异常都会执行 num = 100; System.out.println("finally块执行:num = " + num); return num; // finally块中包含return语句 } } }

    执行流程分析

  • main调用calculateNumber(5,0),进入方法后num=0;

  • try块执行5/0,触发ArithmeticException,跳转到catch块;

  • catch块:num=-1,打印“catch块执行:捕获到算术异常,num = -1”;

  • 执行finally块(必执行):num=100,打印“finally块执行:num = 100”,然后return 100;

  • main接收返回值100,打印“最终返回值:100”。

  • 最终输出

    catch块执行:捕获到算术异常,num = -1 finally块执行:num = 100 最终返回值:100

    关键结论

    ✅ finally里的return优先级最高,会直接覆盖try/catch中修改的变量值,实际开发中尽量避免在finally里写return!

    三、进阶坑:try/catch/finally的return优先级对比

    这道题拆解两种经典场景:“try/catch有return,finally无return” vs “try有return,finally也有return”。

    题目代码

    public class TryCatchReturnAnalysis { public static void main(String[] args) { int result1 = testMethod1(5); System.out.println("testMethod1 返回值:" + result1); int result2 = testMethod1(0); System.out.println("testMethod1 返回值:" + result2); int result3 = testMethod2(); System.out.println("testMethod2 返回值:" + result3); } // 方法1:try含return,catch含return,finally无return public static int testMethod1(int num) { try { int res = 10 / num; System.out.println("try块执行:res = " + res); return res; // try块的return } catch (ArithmeticException e) { System.out.println("catch块执行:捕获算术异常"); return -1; // catch块的return } finally { System.out.println("finally块执行:testMethod1 必执行代码"); // 注意:此处无return } } // 方法2:try含return,finally也含return public static int testMethod2() { int a = 10; try { System.out.println("try块执行:a = " + a); return a++; // try块return,a先返回再自增 } finally { a++; System.out.println("finally块执行:a = " + a); return a; // finally块的return覆盖try的return } } }

    执行流程+输出结果

    1. testMethod1(5)(无异常)
    • try块:10/5=2,打印“try块执行:res = 2”,准备return 2;

    • 先执行finally块:打印“finally块执行:testMethod1 必执行代码”;

    • 再返回2,main打印“testMethod1 返回值:2”。

    2. testMethod1(0)(有异常)
    • try块:10/0触发异常,跳转到catch块;

    • catch块:打印“catch块执行:捕获算术异常”,准备return -1;

    • 先执行finally块:打印“finally块执行:testMethod1 必执行代码”;

    • 再返回-1,main打印“testMethod1 返回值:-1”。

    3. testMethod2()(finally覆盖return)
    • try块:a=10,打印“try块执行:a = 10”,准备return a++(先返回10,再自增为11);

    • 执行finally块:a++(11→12),打印“finally块执行:a = 12”,然后return 12;

    • main打印“testMethod2 返回值:12”。

    最终输出

    try块执行:res = 2 finally块执行:testMethod1 必执行代码 testMethod1 返回值:2 catch块执行:捕获算术异常 finally块执行:testMethod1 必执行代码 testMethod1 返回值:-1 try块执行:a = 10 finally块执行:a = 12 testMethod2 返回值:12

    关键结论

  • 无异常时:try的return → 先执行finally → 再返回;

  • 有异常时:catch的return → 先执行finally → 再返回;

  • finally的return会直接覆盖try的return,且a++是“后自增”,返回的是自增前的值。

  • 四、高阶坑:嵌套try-catch + 异常传递

    这道题考验“内层异常向外层传递”+“finally修改变量但无return”的场景,是面试高频考点。

    题目代码

    public class NestedTryCatchAnalysis { public static void main(String[] args) { int finalResult = outerMethod(); System.out.println("main方法最终接收值:" + finalResult); } // 外层方法:包含外层try-catch-finally public static int outerMethod() { int outerNum = 10; try { System.out.println("外层try块执行:outerNum = " + outerNum); // 调用内层方法,触发异常 int innerResult = innerMethod(0); outerNum += innerResult; return outerNum; // 外层try的return(理论上不会执行) } catch (NullPointerException e) { System.out.println("外层catch块执行:捕获空指针异常"); outerNum = -20; return outerNum; // 外层catch的return } finally { outerNum = 100; System.out.println("外层finally块执行:outerNum = " + outerNum); // 此处无return,但修改了变量值 } } // 内层方法:包含内层try-catch-finally,主动抛异常 public static int innerMethod(int num) { int innerNum = 5; try { System.out.println("内层try块执行:innerNum = " + innerNum); int res = innerNum / num; // 触发算术异常 return res; // 内层try的return(不会执行) } catch (ArithmeticException e) { System.out.println("内层catch块执行:捕获算术异常"); innerNum = -5; // 主动抛出空指针异常,传递给外层 throw new NullPointerException("内层方法抛出空指针"); } finally { innerNum = 50; System.out.println("内层finally块执行:innerNum = " + innerNum); // 此处无return } } }

    执行流程+输出结果

  • main调用outerMethod(),进入外层try块:

  • 打印“外层try块执行:outerNum = 10”;

  • 调用innerMethod(0)。

  • 执行innerMethod(0):

  • 内层try块:打印“内层try块执行:innerNum = 5”,5/0触发算术异常;

  • 内层catch块:打印“内层catch块执行:捕获算术异常”,innerNum=-5,主动抛出NullPointerException;

  • 内层finally块:innerNum=50,打印“内层finally块执行:innerNum = 50”;

  • 异常向外层传递。

  • 回到outerMethod()的外层catch块(捕获空指针):

  • 打印“外层catch块执行:捕获空指针异常”,outerNum=-20,准备return -20;

  • 执行外层finally块:

  • outerNum=100,打印“外层finally块执行:outerNum = 100”;

  • 外层catch的return生效,返回-20,main打印“main方法最终接收值:-20”。

  • 最终输出

    外层try块执行:outerNum = 10 内层try块执行:innerNum = 5 内层catch块执行:捕获算术异常 内层finally块执行:innerNum = 50 外层catch块执行:捕获空指针异常 外层finally块执行:outerNum = 100 main方法最终接收值:-20

    关键结论

    ✅ 内层异常可以通过throw主动向外层传递,外层需捕获对应类型的异常;

    ✅ finally修改变量但无return时,不会影响已经准备好的return值(外层catch已准备return -20,finally改100也没用)。

    📚 核心知识点总结

  • finally执行时机:无论try/catch是否触发异常,finally块一定执行(除非JVM崩溃);

  • return优先级:finally中的return > try/catch中的return,且finally无return时,不会覆盖已准备好的return值;

  • 自定义异常:继承RuntimeException(运行时异常)无需强制声明,贴合业务场景的异常能让代码更易读;

  • 嵌套异常:内层异常可通过throw向外传递,外层需捕获对应类型,是多层方法调用中异常处理的核心逻辑。

  • 如果这4道题帮你打通了异常的“任督二脉”,记得点赞+收藏👍!有任何看不懂的地方,评论区留言,咱们一起拆解~

    赞(0)
    未经允许不得转载:171主机测评 » Java异常终极试炼:4道经典练习题拆解,彻底搞懂try-catch-finally执行顺序!
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址