news 2026/6/10 6:59:02

Java异常处理总结

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java异常处理总结

Java异常处理总结

作者:没有四次元口袋的蓝胖
日期:2026-06-09
标签:Java, 异常处理

一、异常体系架构

1.1 整体继承树

Throwable ├── Error(严重错误,程序不该捕获) │ ├── StackOverflowError │ ├── OutOfMemoryError │ └── ... └── Exception(可处理的异常) ├── RuntimeException(非检查型/未检查异常) │ ├── NullPointerException │ ├── ArrayIndexOutOfBoundsException │ ├── ClassCastException │ ├── ArithmeticException │ ├── NumberFormatException │ └── ... └── 非RuntimeException(检查型异常/受检异常) ├── IOException ├── SQLException ├── FileNotFoundException ├── ClassNotFoundException └── ...

1.2 三大分支一句话区分

类型特点举例要不要处理
ErrorJVM层面的严重问题,程序无法恢复OOM、栈溢出不该捕获,只能预防
受检异常(Checked)编译器强制要求处理,不处理编译不通过IOException、SQLException必须try-catch或throws
非受检异常(Unchecked)编译器不强制,运行时才暴露NPE、数组越界、类型转换建议处理,但不强制

面试必问:Error和Exception的区别?
→ Error是程序无法处理的严重问题(JVM级),Exception是程序可以处理的异常。实际开发中我们只处理Exception,Error出现了基本就是代码有bug或资源不够。

面试追问:受检异常和非受检异常的区别?
→ 核心区别是编译器是否强制处理。受检异常必须在代码中显式try-catch或throws声明,否则编译失败;非受检异常(RuntimeException及其子类)编译器不强制,但运行时一样会抛出。设计上,受检异常是"可预期的合理异常"(如文件找不到),非受检异常是"编程错误"(如空指针)。


二、异常处理的五种方式

2.1 try-catch-finally

try{// 可能出问题的代码intresult=10/0;}catch(ArithmeticExceptione){// 处理特定异常System.out.println("算术异常:"+e.getMessage());}catch(Exceptione){// 处理其他异常(子类在前,父类在后)System.out.println("其他异常:"+e.getMessage());}finally{// 无论如何都执行(除非JVM退出)System.out.println("释放资源");}

catch顺序规则:子类在前,父类在后。如果把Exception写在ArithmeticException前面,编译报错——因为Exception已经涵盖了子类,后面的catch永远走不到,这是"不可达代码"。

2.2 try-catch-finally的执行顺序——面试高频陷阱

情况1:try正常执行,无异常

try → finally

情况2:try有异常,catch匹配到

try(异常处中断)→ catch → finally

情况3:try有异常,catch没匹配到

try(异常处中断)→ finally → 异常向上抛

情况4:catch中也有异常

try → catch(新异常)→ finally → catch中的新异常向上抛

情况5:finally中有return——绝对不要这么写

publicstaticinttest(){try{return1;}finally{return2;// 会覆盖try中的return 1}}// 结果:返回2,且try中的返回值被吞掉

面试经典题:

publicstaticinttest(){inta=10;try{a=20;returna;// 此时已经确定了返回值20的"快照"}finally{a=30;// 修改了a,但不影响返回值}}// 结果:返回20,不是30

结论:finally中的修改不会影响try/catch中已经确定的返回值(基本类型)。但如果是引用类型,修改对象的属性是会影响返回结果的。

2.3 throws——声明异常

// 方法签名上声明可能抛出的异常,交给调用者处理publicvoidreadFile(Stringpath)throwsIOException,FileNotFoundException{// 读取文件}

throws不是"处理"异常,是"甩锅"——告诉调用者"我这个方法可能出这些异常,你来处理"。调用者要么继续throws,要么try-catch。

throws的规则:

  • 受检异常必须声明,非受检异常可选(但建议也写上,文档意义)
  • 重写方法时,子类throws的异常范围不能超过父类(可以更小或不抛)

2.4 throw——主动抛出异常

publicvoidsetAge(intage){if(age<0||age>150){thrownewIllegalArgumentException("年龄不合法:"+age);}this.age=age;}

throw vs throws 区别:

对比throwthrows
位置方法体内部方法签名上
作用主动抛出一个异常对象声明方法可能抛出的异常类型
数量一次只能抛一个可以声明多个
跟谁配合后面跟异常对象new XxxException()后面跟异常类名

2.5 try-with-resources(JDK 7+)——自动关闭资源

// 传统写法:啰嗦,容易漏关Connectionconn=null;try{conn=DriverManager.getConnection(url);// 使用连接}catch(SQLExceptione){e.printStackTrace();}finally{if(conn!=null){try{conn.close();// close也可能抛异常!}catch(SQLExceptione){e.printStackTrace();}}}// try-with-resources:简洁、安全try(Connectionconn=DriverManager.getConnection(url);Statementstmt=conn.createStatement();ResultSetrs=stmt.executeQuery(sql)){// 使用资源,结束后自动关闭}catch(SQLExceptione){e.printStackTrace();}// 无需finally,自动按声明的逆序关闭

原理:资源必须实现AutoCloseable接口(或其父接口Closeable),try结束时自动调用close()方法。

JDK 9增强:允许在try外声明变量,直接引用

Connectionconn=DriverManager.getConnection(url);try(conn){// JDK 9+,直接引用已声明的变量// 使用连接}

面试追问:“try-with-resources中如果try块和close都抛异常,哪个生效?”
→ try块的异常是主异常,close的异常会被抑制(suppressed),通过Throwable.getSuppressed()可以获取。这是try-with-resources比手动finally关闭的一个重要优势——finally中close的异常会覆盖try中的原始异常,导致问题难以定位。


三、常见异常速查

3.1 非受检异常(RuntimeException家族)

异常触发场景预防方式
NullPointerException调用null对象的成员前置判空 / Optional
ArrayIndexOutOfBoundsException数组下标越界检查length
ClassCastException强制类型转换失败先用instanceof判断
ArithmeticException整数除以0除前检查
NumberFormatException字符串转数字失败正则预校验或try-catch
StringIndexOutOfBoundsException字符串下标越界检查length
ConcurrentModificationException遍历集合时修改集合用迭代器删除 / CopyOnWrite
IllegalArgumentException参数不合法参数校验

3.2 受检异常(编译器强制处理)

异常触发场景
IOExceptionIO操作失败(文件、网络)
FileNotFoundException文件不存在
SQLException数据库操作失败
ClassNotFoundException类加载失败
InterruptedException线程被中断
CloneNotSupportedException不支持克隆

四、自定义异常

4.1 为什么要自定义?

JDK的异常类是通用的,无法表达业务语义。比如"余额不足"用ArithmeticException显然不合适,自定义InsufficientBalanceException一眼就懂。

4.2 怎么自定义?

// 自定义受检异常publicclassBusinessExceptionextendsException{privateintcode;publicBusinessException(Stringmessage){super(message);}publicBusinessException(intcode,Stringmessage){super(message);this.code=code;}publicintgetCode(){returncode;}}// 自定义非受检异常publicclassParamInvalidExceptionextendsRuntimeException{publicParamInvalidException(Stringmessage){super(message);}}

选型:继承Exception还是RuntimeException?

  • 业务校验失败(参数错误、余额不足)→ 继承RuntimeException,不强制上层处理,由全局异常处理器统一捕获
  • 必须让调用者处理的异常 → 继承Exception
  • 实际开发中,自定义异常大部分继承RuntimeException,配合Spring的@ControllerAdvice全局处理

五、异常处理最佳实践

5.1 七个不要

① 不要吞掉异常

// 反例:异常被吞,出问题完全没线索try{doSomething();}catch(Exceptione){// 空catch块,最差写法}// 正例:至少记录日志try{doSomething();}catch(Exceptione){log.error("操作失败",e);}

② 不要用异常控制流程

// 反例:用异常代替条件判断,性能差、可读性差try{list.get(index);}catch(IndexOutOfBoundsExceptione){// 处理越界}// 正例:先检查条件if(index>=0&&index<list.size()){list.get(index);}

③ 不要在finally中return——前面已经讲过,会覆盖原始返回值和异常。

④ 不要catch范围过大

// 反例:一把梭Exceptioncatch(Exceptione){...}// 正例:捕获具体类型catch(IOExceptione){...}catch(SQLExceptione){...}

⑤ 不要重复记录同一异常——catch中log一次,throws出去调用者又log一次,日志里全是重复堆栈。

⑥ 不要用e.printStackTrace()——生产环境应该用日志框架(SLF4J/Logback),printStackTrace输出到System.err,不可控。

⑦ 不要在循环里频繁try-catch——把try-catch移到循环外面,除非每次迭代的异常需要单独处理。

5.2 五个要

① 要用try-with-resources管理资源——替代手动finally关闭,简洁且不会丢失原始异常。

② 要给异常带上下文信息

// 反例:只抛异常,不知道哪个文件出问题thrownewFileNotFoundException();// 正例:带上具体信息thrownewFileNotFoundException("配置文件不存在: "+configPath);

③ 要在合适的层次处理异常——底层抛出,业务层捕获并转换,最上层统一响应。不要在底层吞掉,也不要在最底层就返回用户错误码。

④ 要优先校验而非依赖异常——前置条件检查比事后捕获更高效、更清晰。

⑤ 要对受检异常做合理包装

// 反例:层层throws IOException,上层被迫处理publicvoidbusinessMethod()throwsIOException{...}// 正例:包装成业务异常publicvoidbusinessMethod(){try{readFile();}catch(IOExceptione){thrownewBusinessException("读取配置失败",e);// 保留原始异常链}}

注意保留异常链!new BusinessException("msg", e)把原始异常e传进去,这样日志里能看到完整调用链。如果只写new BusinessException("msg")就丢了根因。


六、面试必背题

Q1:Java异常体系的顶层类是什么?分几大类?

Throwable,分Error和Exception。Exception再分受检(Checked)和非受检(Unchecked/RuntimeException)。

Q2:finally块一定执行吗?

几乎一定,但有例外:

  • System.exit(0)杀掉JVM,finally不执行
  • 执行try/catch的线程被杀死或中断
  • 守护线程中的finally可能不会执行(JVM退出时)

Q3:try里有return,finally还执行吗?返回值是什么?

执行。finally在return之后、方法真正返回之前执行。如果finally中没有return,不影响try/catch中的返回值(基本类型);如果finally中有return,会覆盖原返回值。

Q4:受检异常和非受检异常的区别?举例说明。

受检异常编译器强制处理(try-catch或throws),如IOException;非受检异常编译器不强制,如NullPointerException。设计意图:受检异常是"可预期的外部问题",非受检异常是"编程错误"。

Q5:throw和throws的区别?

throw在方法体内,主动抛出一个异常对象;throws在方法签名上,声明该方法可能抛出的异常类型。throw一次一个,throws可以声明多个。

Q6:try-with-resources比try-finally好在哪里?

  1. 代码简洁,不需要手动关闭
  2. 自动按声明逆序关闭多个资源
  3. close的异常不会覆盖try中的原始异常(被suppressed保留)
  4. 不会因为忘写finally导致资源泄漏

思维导图速览

Java异常处理 ├── 一、体系架构 │ ├── Throwable │ │ ├── Error(不可处理:OOM、StackOverflow) │ │ └── Exception │ │ ├── 受检异常(必须处理:IOException) │ │ └── 非受检异常(不强制:NPE) │ └── 区别核心:编译器是否强制处理 ├── 二、五种处理方式 │ ├── try-catch-finally(执行顺序是面试重点) │ ├── throws(声明/甩锅) │ ├── throw(主动抛出) │ └── try-with-resources(自动关资源,优先用) ├── 三、常见异常 │ ├── RuntimeException:NPE/越界/类型转换/参数非法 │ └── Checked:IO/SQL/类加载/中断 ├── 四、自定义异常 │ ├── 继承Exception → 受检 │ └── 继承RuntimeException → 非受检(推荐) ├── 五、最佳实践 │ ├── 七不要:吞异常/异常控流程/finally return/... │ └── 五要:with-resources/带上下文/合适层次/... └── 六、面试必背六题 ├── 体系分类 ├── finally执行时机 ├── try里return与finally ├── 受检vs非受检 ├── throw vs throws └── try-with-resources优势

写在最后

异常处理是Java基本功,面试考法很固定:体系结构→执行顺序→最佳实践。核心要记住:

  1. 体系图要能画出来——Throwable分Error和Exception,Exception再分受检和非受检,这是所有异常题的基础
  2. finally的执行顺序是高频考点——特别是"try中有return,finally还执行吗"和"finally中return会覆盖"这两道变体题
  3. try-with-resources要会写——JDK 7就有了,但很多人面试时还是只写try-finally,显得不够现代
  4. 最佳实践不是空话——“不要吞异常”"不要用异常控制流程"这些在代码审查中天天见,面试说了说明你写过生产代码
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/10 6:57:29

调查研究-165 vLLM 深入浅出:从 PagedAttention 到生产级大模型推理服务

vLLM 深入浅出&#xff1a;从 PagedAttention 到生产级大模型推理服务TL;DR 如果你正在本地或服务器上部署大语言模型&#xff0c;迟早会遇到三个问题&#xff1a;显存不够、并发上不去、接口不好接。模型本身只是第一步&#xff0c;真正让模型稳定对外提供服务的是推理引擎。 …

作者头像 李华
网站建设 2026/6/10 6:49:59

最新 macOS 27 页面新变化,先睹为快!

一、状态栏变化状态栏可以手动/自动折叠隐藏&#xff0c;状态栏可以跨过刘海显示&#xff0c;不用再第三方软件去折叠显示了。二、新图标变化状态栏电池出现了新图标。Siri 变成单独应用&#xff0c;出现新图标。应用展现新图标&#xff0c;本人感觉对比度和磨砂质感更高了&…

作者头像 李华
网站建设 2026/6/10 6:49:46

深入理解 MCP 协议:从底层通信到 MySQL 实战接入

深入理解 MCP 协议&#xff1a;从底层通信到 MySQL 实战接入最近在做一个课设&#xff0c;想让 AI 能直接查询我本地的 MySQL 数据库。网上翻了一圈&#xff0c;全是"MCP 是 AI 的 USB-C 接口"这类比喻&#xff0c;看完还是不知道怎么用。花了三天踩坑&#xff0c;把…

作者头像 李华
网站建设 2026/6/10 6:48:16

关于借用GitHub来实现自动更新的方法

最近在研究怎么让一个程序能自动检测更新&#xff0c;我有一个比较有意思的想法想分享一下&#xff0c;常规的自动更新方式&#xff0c;无非主要思路在于&#xff0c;程序持有者需要和远端服务器通信&#xff0c;判断当前应用版本号是否为最新&#xff0c;如若远端服务器返回否…

作者头像 李华
网站建设 2026/6/10 6:45:52

Codex 使用指南:从入门到高效协作,让 AI 真正参与开发流程

摘要Codex 不只是一个“帮你写代码”的工具&#xff0c;更像是一个可以理解项目、修改文件、运行命令、定位问题、补充测试和做代码审查的 AI 编程协作者。本文将从实际开发场景出发&#xff0c;介绍 Codex 的常见用法、提示词写法、协作流程以及避坑建议&#xff0c;帮助你更稳…

作者头像 李华