各位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道题帮你打通了异常的“任督二脉”,记得点赞+收藏👍!有任何看不懂的地方,评论区留言,咱们一起拆解~




