
目录
高频面试题:try 里面 return,finally 还会执行吗?
本节学习目标
- 理解异常概念、
Throwable完整继承体系,分清Error和Exception - 区分受检异常(编译时异常)、非受检异常(运行时异常 RuntimeException)
- 掌握五大关键字:
throw、throws、try‑catch、finally - 吃透异常向上传递调用栈、完整异常处理流程
- 学会编写自定义异常,区分受检、非受检自定义异常
- 熟悉面试高频坑点:
finally与return共存的执行逻辑
1. 异常的概念与体系结构
1.1 什么是异常
异常:程序运行过程中发生非正常错误状态,打断正常执行流程,可以通过 Java 代码捕获处理。
注意:编译语法错误(比如单词拼写错)不属于异常,编译直接失败,根本不会生成 class 字节码。
常见运行异常示例:
- 算术异常
ArithmeticExceptio - 数组下标越界
ArrayIndexOutOfBoundsException
public static void main(String[] args) {
int[] array = {1, 2, 3};
System.out.println(array[100]);
}
- 空指针异常
NullPointerException
public static void main(String[] args) {
int[] array = null;
System.out.println(array.length);
}
1.2 异常继承体系

顶层父类:Throwable,派生出两大分支:Error、Exception。
- Error(错误) JVM 层面严重错误,程序代码无法处理,无力回天。 典型例子:
StackOverflowError栈溢出(递归太深)OutOfMemoryError内存耗尽。
出现 Error,程序只能终止,写 try‑catch 也救不了。
- Exception(异常) 程序员可以编写代码捕获、修复,让程序继续运行。分为两大类:
- 受检异常(Checked Exception / 编译时异常):除
RuntimeException之外的 Exception 子类。编译阶段编译器强制要求处理,要么try‑catch捕获,要么throws声明抛出。 - 非受检异常(Unchecked Exception / 运行时异常):
RuntimeException以及它的所有子类。编译不强制处理,运行才会报错。
常见运行时异常:
NullPointerException、ArrayIndexOutOfBoundsException、ArithmeticException、ClassCastException。
示例:受检异常编译报错
class Person{
@Override
public Person clone() {
// CloneNotSupportedException是受检异常,编译器强制处理
return (Person)super.clone();
}
}
//编译报错:未报告的异常错误java.lang.CloneNotSupportedException; 必须对其进行捕获或声明以便抛出
2. 异常处理 5 大关键字
throw、throws、try、catch、finally
2.1 throw:手动抛出异常对象(写在方法体内)
作用:代码内部检测到错误,手动创建异常对象抛出去,通知方法调用者。
语法:
throw new XXXException("异常描述信息");
示例:获取数组指定下标元素,参数合法性校验
public static int getElement(int[] array, int index){
if(null == array){
throw new NullPointerException("传递的数组为null");
}
if(index < 0 || index >= array.length){
throw new ArrayIndexOutOfBoundsException("传递的数组下标越界");
}
return array[index];
}
public static void main(String[] args) {
int[] array = {1,2,3};
getElement(array, 3);
}
throw 注意事项:
throw只能写在方法体内部。- 抛出必须是
Exception或者它的子类实例对象。 - 如果抛出运行时异常(RuntimeException),编译器不强制处理,直接交给上层调用者。
- 如果抛出受检异常,必须
try‑catch捕获,或者方法上写throws声明,否则编译报错。 throw执行之后,本行后面的代码不会执行。
2.2 throws:方法声明上声明异常(写在方法签名后)
throw是抛出异常对象;throws是告诉调用者:本方法有可能产生哪些异常,把处理责任交给调用方。
语法:
修饰符 返回类型 方法名(参数列表) throws 异常1,异常2{
}
示例:打开配置文件,文件找不到是受检异常
import java.io.FileNotFoundException;
public class Config {
//声明该方法可能抛出FileNotFoundException
public void openConfig(String filename) throws FileNotFoundException{
if(!filename.equals("config.ini")){
throw new FileNotFoundException("配置文件名字不对");
}
}
}
throws 重点规则:
- 写在方法参数列表圆括号之后。
- 声明的类型必须是
Exception或者子类。 - 方法内部抛出多个异常,可以逗号隔开写多个;如果异常存在父子继承关系,可以直接写父类异常,简化代码。
import java.io.IOException;
public class Config {
//FileNotFoundException是IOException子类,直接声明父类IOException即可
public void openConfig(String filename) throws IOException{
throw new FileNotFoundException("配置文件名字不对");
}
}
- 调用带有
throws声明受检异常的方法,调用者二选一:- 方案①:调用处使用
try‑catch捕获处理; - 方案②:调用者方法继续追加
throws,继续往上抛。
- 方案①:调用处使用
//方案2 main继续往上抛,异常交给JVM
public static void main(String[] args) throws IOException {
Config config = new Config();
config.openConfig("config.ini");
}

IDEA 快捷键:光标放在报错代码上,
Alt+Insert快速选择:①添加 throws 声明;②生成 try‑catch 包裹代码。
2.3 try‑catch 捕获处理异常
throws只是向外通告异常,不会真正解决问题;try‑catch 才是真正处理异常。
完整语法格式
try{
// 有可能发生异常的业务代码
}catch(异常类型 异常对象){
//匹配该异常之后执行处理逻辑
}catch(异常类型 异常对象){
//多种异常对应多个catch块
}finally{
//无论是否发生异常,这里代码一定会执行(除极端JVM退出)
}
// try‑catch之后的代码,如果异常被捕获处理,会正常执行
示例:捕获文件找不到异常
import java.io.FileNotFoundException;
public class Config {
public void openConfig(String filename) throws FileNotFoundException{
if(!filename.equals("config.ini")){
throw new FileNotFoundException("配置文件名字不对");
}
}
public static void main(String[] args) {
Config config = new Config();
try {
config.openConfig("config.txt");
System.out.println("文件打开成功");
} catch (FileNotFoundException e) {
//打印异常信息三种方式
System.out.println(e.getMessage()); //只获取异常描述文字
System.out.println(e); //异常类名 + 异常信息
e.printStackTrace(); //⭐最常用:打印完整调用栈信息,定位出错行号
}
//异常被捕获处理,后续代码继续执行
System.out.println("异常处理完毕,程序继续往下跑");
}
}
try‑catch 重要坑点与规则
- try 块中,一旦某行抛出异常,这行后面 try 内剩余代码不再执行,直接跳到 catch 匹配。
- catch 是按异常类型匹配,抛出异常和 catch 声明类型一致、或是它的子类,才会被捕获。类型不匹配则不会捕获,异常继续向上抛出。
❌错误示例:异常类型不匹配,捕获失效
public static void main(String[] args) {
try {
int[] array = {1,2,3};
System.out.println(array[3]); //数组越界异常
} catch (NullPointerException e) { //捕获空指针,类型对不上
e.printStackTrace();
}
System.out.println("后续代码"); //不会打印,异常交给JVM程序终止
}
- 多个异常分开多个 catch 块;子类异常 catch 必须写在父类异常前面!
如果父类 Exception 写在前边,后面子类 catch 块永远执行不到,编译报错。
✅正确顺序:子类在前,父类在后
public static void main(String[] args) {
int[] array = {1, 2, 3};
try {
System.out.println(array[100]);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("数组下标越界");
e.printStackTrace();
} catch (NullPointerException e) {
System.out.println("空指针异常");
e.printStackTrace();
}
System.out.println("try‑catch执行结束");
}
- 多个异常处理逻辑完全一样,可以使用
|竖线合并同一个 catch
catch (ArrayIndexOutOfBoundsException | NullPointerException e) {
e.printStackTrace();
}
- 直接捕获大父类
Exception可以捕获全部异常,业务开发不推荐,会掩盖真实错误,调试不方便。
//不推荐,一网打尽,分不清是什么错误
try{
}catch(Exception e){
e.printStackTrace();
}
2.4 finally 代码块
finally:无论是否抛出异常,绝大多数场景一定会执行。 核心用途:资源释放!关闭文件流、Scanner、数据库连接、网络 socket。 因为 try 里面一旦 return,try 后面普通代码不会跑,但是 finally 依旧执行。
示例场景:Scanner 资源释放
import java.util.InputMismatchException;
import java.util.Scanner;
public class TestFinally {
public static int getData(){
Scanner sc = null;
try{
sc = new Scanner(System.in);
int data = sc.nextInt();
return data; //这里return,方法准备返回
}catch (InputMismatchException e){
e.printStackTrace();
}finally {
//不管正常return,还是异常,这里都会执行,用来关闭资源
System.out.println("finally 执行,释放资源");
if(sc != null){
sc.close();
}
}
return 0;
}
public static void main(String[] args) {
int num = getData();
System.out.println(num);
}
}
高频面试题:try 里面 return,finally 还会执行吗?
finally 在方法真正 return 返回之前执行。 ⚠️强烈禁止在 finally 里面写 return!会直接覆盖 try/catch 中的 return 返回值。
public static int func() {
try {
return 10;
} finally {
//禁止在这里写return!
return 20;
}
}
public static void main(String[] args) {
System.out.println(func()); //输出20,try的return 10被覆盖
}
开发规范:
finally只做资源清理,不要写 return,编译器会报警告。
2.5 异常完整向上传递流程(调用栈)
- JVM 有虚拟机栈,记录每一层方法调用关系,称为调用栈。
- 当前方法抛出异常,如果本层没有合适 catch 捕获,异常顺着调用栈向上一层调用方法传递。
- 一层层向上传递,如果到 main 方法依旧没有捕获,交给 JVM 处理:打印异常栈信息,程序直接终止,后面代码不再运行。
示例:异常向上抛
public static void main(String[] args) {
try {
func();
} catch (ArrayIndexOutOfBoundsException e) {
e.printStackTrace();
}
System.out.println("main后续代码,可以执行");
}
public static void func() {
int[] array = {1, 2, 3};
System.out.println(array[100]); //本方法没有catch,向上抛给main
}
完整流程总结:
- 执行 try 里面代码;
- try 内部出现异常,立刻终止 try 剩余代码;匹配当前方法 catch;
- 匹配成功,执行 catch;匹配失败,异常向上层调用者抛出;
- 无论是否捕获异常,finally 块在本方法结束前一定执行;
- 持续向上传递,如果 main 方法也没有处理,JVM 接管,程序终止。
3. 自定义异常
Java 内置异常不能覆盖全部业务场景,比如登录业务:用户名错误、密码错误,需要业务自定义异常。
自定义异常规则:
- 继承
Exception→ 受检异常(编译强制处理) - 继承
RuntimeException→ 非受检运行时异常,编译不强制处理 - 必须实现一个带 String message 参数的构造方法,调用父类 super (message) 保存异常信息。
示例:登录业务自定义异常
//用户名错误:受检异常,继承Exception
class UserNameException extends Exception {
public UserNameException(String message) {
super(message);
}
}
//密码错误
class PasswordException extends Exception {
public PasswordException(String message) {
super(message);
}
}
public class Login {
private String userName = "admin";
private String password = "123456";
//声明可能抛出两个自定义受检异常
public void loginInfo(String userName, String password) throws UserNameException, PasswordException{
if (!this.userName.equals(userName)) {
throw new UserNameException("用户名错误!");
}
if (!this.password.equals(password)) {
throw new PasswordException("密码错误!");
}
System.out.println("登陆成功");
}
public static void main(String[] args) {
Login login = new Login();
try {
login.loginInfo("admin111", "123456");
} catch (UserNameException e) {
e.printStackTrace();
} catch (PasswordException e) {
e.printStackTrace();
}
}
}
开发选择经验:
- 业务错误希望编译阶段强制提醒调用方处理:继承
Exception受检异常。- 属于程序 bug(参数非法),不需要编译强制检查:继承
RuntimeException运行时异常。
课后选择题

答案 1.C 2.AD 3.D
📔本篇核心考点总结
Throwable分两大分支:Error虚拟机严重错误无法处理;Exception程序员可处理异常。- 受检异常(Checked):除 RuntimeException 以外的 Exception 子类,编译强制处理;运行时异常 RuntimeException 子类,编译不强制处理。
throw:方法体内手动抛出异常对象;throws写在方法签名,声明该方法可能抛出的异常,把责任交给调用者。try‑catch真正捕获处理异常;catch 捕获匹配看类型;子类异常 catch 必须写在父类前面。finally主要用于资源释放,绝大多数场景必执行;禁止在 finally 写 return,会覆盖原有返回值。- 异常没有被当前 catch 捕获,沿着调用栈向上传递;main 也不处理交给 JVM,程序终止。
- 自定义异常:继承
Exception是受检;继承RuntimeException是非受检;需要带 message 构造。
面试简答 Q:throw 和 throws 区别?
A:throw 在方法内部,手动抛出一个异常实例对象;throws 写在方法声明之后,向外声明本方法可能抛出哪些异常,提醒调用者处理。
Q:finally 一定会执行吗?什么情况不执行?
A:绝大多数情况会执行;只有
System.exit(0)直接终止 JVM 虚拟机的时候,finally 不会跑。
Q:受检异常和非受检异常区别?
A:受检异常编译阶段强制 try‑catch 或者 throws;RuntimeException 运行时异常编译不强制处理,运行时才报错。

转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/wuqingyigexiaoba/article/details/165751455




