news 2026/6/18 16:57:59

Java 异常 详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java 异常 详解

Java 异常 详解

一、什么是 Java 异常?

异常就是程序运行时出现的意外情况,会中断正常的代码执行流程。
出现异常后,正常流程会中断,但程序不会直接崩溃,Java 会启动专门的异常处理流程,用来容错、记录错误、恢复程序。

所有异常和错误的顶层父类都是Throwable,整体结构:

  1. Error(错误):JVM 系统级错误,代表 JVM 运行环境故障,如内存溢出、栈溢出。代码无法修复,我们无法处理,不需要捕获;

  2. Exception(异常)[经常发生]:代码异常,我们可以捕获和处理,分为两类:

    • 受检异常/受控异常RuntimeException编译期异常):编译时强制处理,如 IO 异常、SQL 异常;
    • 非受检异常/非受控异常(运行时异常)[极少发生]:代码逻辑 BUG,如空指针、数组越界,不强制捕获。

    注意:两种异常都在运行是才检测发生的,不要被名字迷惑了。
    关于异常,对于经常发生的,我们要时常预防(就如同流感,满足“条件”就“发病”,所以要“打疫苗”),对于极少发生的,人为能够避免的(比如除零)(如同代码得了绝症)我们不处理,这就是我们面对异常的态度。


二、try-catch 核心用法

try-catch是 Java 异常处理的核心语法,用来捕获并处理异常,避免程序直接崩溃。

1. 标准语法结构

try{// 可能出错的代码}catch(异常类型 e){// 出错了在这里处理}

或者更进一步的解释:

try{// 1. 只包裹可能抛出异常的代码(粒度最小化)// 2. 一旦这里发生异常,本行之后的代码立刻停止执行}catch(指定异常类型 变量名){// 3. 捕获后必须打印日志/输出提示,不能空捕获// 4. 针对性处理异常,提高程序健壮性}

2. 完整代码示例

案例 1:数组越界(RuntimeException)

publicclassDemo{publicstaticvoidmain(String[]args){int[]arr={10,20,30};try{System.out.println(arr[10]);System.out.println("异常后代码不会执行");}catch(ArrayIndexOutOfBoundsExceptione){System.out.println("捕获运行时异常:数组下标越界!");}System.out.println("程序正常运行,没有崩溃");}}
捕获异常:数组下标越界! 程序继续运行 案例 2:空指针异常(RuntimeException)
Stringname=null;try{name.length();}catch(NullPointerExceptione){System.out.println("捕获运行时异常:对象为空,无法调用方法");}
捕获异常:对象为空! 程序没有崩溃

案例 3:文件不存在(受检 Exception)

importjava.io.FileInputStream;publicclassTestFile{publicstaticvoidmain(String[]args){try{// FileNotFoundException 属于受检异常,不加try-catch直接编译报错!!!FileInputStreamfis=newFileInputStream("test.txt");}catch(FileNotFoundExceptione){System.out.println("捕获受检异常:文件不存在");}}}
捕获受检异常:文件不存在

3. 为维持代码的健壮性

  1. try 范围最小化:只包裹可能出错的代码,不把整个方法包起来,方便精准定位异常;
  2. 捕获具体异常:不直接捕获Exception,避免把所有错误一锅端(Exception e);
  3. 不空 catch:必须打印日志或提示,禁止catch {}吃掉异常;
  4. 程序不中断:异常被处理后,后续代码正常运行,提升系统稳定性。

4. 多 catch 标准格式(子类在前,父类在后)

一段代码可能抛出多种异常时,使用多 catch 分别处理,严格遵守子类异常在前,父类兜底在后

try{// 可能抛出多种异常的代码}catch(NullPointerExceptione){System.out.println("空指针异常:对象未初始化");e.printStackTrace();}catch(ArrayIndexOutOfBoundsExceptione){System.out.println("数组越界异常:索引超出范围");e.printStackTrace();}catch(Exceptione){// 父类异常兜底,防止未知异常导致崩溃System.out.println("未知异常,请检查代码");e.printStackTrace();}

三、try-catch-finally

finally是异常处理的收尾模块,专门用于资源释放(关闭文件流、数据库连接、网络连接等),是保证代码健壮性的关键。

1. 标准语法格式

try{// 可能异常的业务代码}catch(Exceptione){// 异常处理逻辑}finally{// 资源释放/清理操作,必定执行}

2. finally基础

publicclassFinallyBaseDemo{publicstaticvoidmain(String[]args){System.out.println("程序开始运行");Stringfile=null;try{System.out.println("打开文件");file="test.txt";// 模拟文件不存在,制造异常if("test.txt".equals(file)){inta=1/0;}System.out.println("读取文件内容");}catch(Exceptione){System.out.println("读取文件发生错误");}finally{// 无论有没有异常,这段代码一定执行System.out.println("执行finally:关闭文件,释放资源");}System.out.println("程序执行结束");}}
程序开始运行 打开文件 读取文件发生错误 执行finally:关闭文件,释放资源 程序执行结束

3. finally 执行

  1. 只要 JVM 不退出,finally 一定执行
  2. try 中无论是否发生异常,finally 都会执行;
  3. try 中即使有return,也会先执行 finally,再执行 return
  4. 唯一不执行的情况:System.exit(0)关闭 JVM。

1.正常执行,规范写法,finally 无 return

publicclassFinallyReturnTest{publicstaticinttest(){try{System.out.println("进入try代码块");// 准备返回值return100;}catch(Exceptione){return-1;}finally{System.out.println("进入finally,一定会执行!");}}publicstaticvoidmain(String[]args){intres=test();System.out.println("方法最终返回值:"+res);}}
进入try代码块 进入finally,一定会执行! 方法最终返回值:100

try 抛出异常 + return,依旧先走 finally

publicclassFinallyExceptionReturnTest{publicstaticinttest(){try{System.out.println("try内部,准备抛出异常");intnum=1/0;// 算术异常return100;}catch(Exceptione){System.out.println("捕获异常");return-1;}finally{System.out.println("无论报错还是return,finally必执行");}}publicstaticvoidmain(String[]args){intres=test();System.out.println("最终返回:"+res);}}
try内部,准备抛出异常 捕获异常 无论报错还是return,finally必执行 最终返回:-1

注:方法执行return时,不会立刻退出,会先完整执行完 finally 代码块,再执行 return 返回结果;如果 finally 内部写了 return,会直接覆盖原有返回值,同时吞噬上层异常,开发规范中严禁这么写。

因此在有finally的情况下,为保持代码健壮性:

  1. 资源必释放:finally 保证流/连接一定会关闭,避免内存泄漏、文件占用;
  2. 空值判断:关闭资源前判断非空,防止空指针;
  3. 不写业务逻辑:finally 只做清理,不影响主流程;
  4. 禁止 return:finally 中写 return 会覆盖返回值、吞噬异常,严重破坏健壮性。

四、throw 和 throws

这两个关键字是异常抛出的核心

1. throws:方法声明异常—— 声明可能抛出的异常

throws 修饰在方法签名末尾,用于提前声明当前方法可能向外抛出的异常类型。表示“调用本方法时,可能会抛出这些异常”,调用者必须处理或继续向上抛出。

  • 位置:方法签名末尾
  • 作用:声明方法可能抛出的异常,交给调用方处理
  • 标准格式:
返回值类型 方法名(参数列表)throws异常类型1,异常类型2{// 方法体 //语法规范:多个异常用逗号分隔,只写异常类名,//不实例化对象。}
// 受检异常必须声明,提高代码可读性和健壮性publicstaticvoidreadFile()throwsIOException{// 方法业务逻辑}

强制使用规范(核心)

  1. 仅针对受检异常必须声明
    JDK 语法强制:所有直接继承Exception的受检异常,不写throws则编译报错。
    底层逻辑:编译器静态检查,强制开发者感知 IO、数据库等不可控外部风险。
  2. 工具层、底层通用方法统一使用throws上抛,不在底层捕获处理
    分层规范:底层只做能力封装,异常交给上层业务统一处理。
  3. 禁止笼统声明throws Exception
    规范约束:精准列出方法实际抛出的异常,便于调用方针对性捕获,避免掩盖未知故障。

特点

  1. 不处理异常,只是预警。
  2. 可以同时声明多个异常。
  3. 调用 throws 声明了受检异常的方法时,调用者必须用 try-catch 捕获,或者自己也用 throws 继续向上抛。

示例

importjava.io.FileInputStream;importjava.io.IOException;publicclassDemo{// 声明:我可能报错,我不处理publicstaticvoidreadFile()throwsIOException{FileInputStreamfis=newFileInputStream("test.txt");}// 调用者必须处理publicstaticvoidmain(String[]args){try{readFile();}catch(IOExceptione){System.out.println("文件读取失败");}}}
文件读取失败

2. throw:手动抛异常—— 实际抛出异常

throw 写在方法内部,用于手动实例化并抛出异常对象,执行后立即中断当前代码流程。会在代码中主动制造并抛出一个异常。

  • 位置:方法内部
  • 作用:主动创建并抛出异常对象,立即中断流程
语法
thrownew异常类名(参数);

强制使用规范

  1. 参数校验、业务状态校验统一使用 throw 主动抛出
    入参为空、参数范围非法、业务状态不满足执行条件,提前抛出异常拦截,不进入后续业务逻辑;
  2. 业务场景优先抛自定义运行时异常,原生运行时异常仅用于基础参数校验;
  3. 抛出异常时必须携带清晰描述信息,禁止throw new RuntimeException(“”)空提示;
  4. 受检异常使用 throw 抛出时,方法必须配套throws声明。

特点

  1. 一旦执行 throw,当前方法立即结束,后面的代码不再执行。
  2. 抛出的异常对象可以是 JDK 内置的,也可以是自己定义的。
  3. 如果抛出的是 受检异常(checked),则当前方法必须用 throws 声明,或者用 try-catch 捕获。

示例

publicintdivide(inta,intb){if(b==0){thrownewArithmeticException("除数不能为零");}returna/b;}

3.作用

  1. 提前校验:throw 用于参数/状态校验,防患于未然
  2. 明确错误信息:抛出带提示的异常,方便快速定位;
  3. throws 明确风险:调用方一看便知需要处理异常,提升协作健壮性。

4. 一句话区分

  • throws:方法声明,我可能抛异常
  • throw:方法内部,我现在抛异常(底层靠 JVM native 实现)

五、自定义异常

1、为什么要自定义异常?

  1. 使错误信息更具体、更有业务含义(如:InsufficientBalanceException 比 IllegalArgumentException 更清晰)。系统异常无法表达业务含义(用户不存在、余额不足、权限不足);
  2. 针对特定场景单独捕获和处理。
  3. 封装额外的错误信息(字段、状态码等)。
  4. 精准区分异常类型,避免用通用 Exception 混淆业务错误。

2. 一般标准格式

// 定义classMyExceptionextendsException{// 或 RuntimeExceptionpublicMyException(Stringmsg){super(msg);}}// 抛出thrownewMyException("自定义错误");// 捕获try{// ...}catch(MyExceptione){e.printStackTrace();}

创建步骤

1). 继承Exception—— 受检异常 (Checked)

调用者必须显式处理(try-catch 或 throws)。

publicclassInvalidAgeExceptionextendsException{publicInvalidAgeException(){super();}publicInvalidAgeException(Stringmessage){super(message);}publicInvalidAgeException(Stringmessage,Throwablecause){super(message,cause);}}

2). 继承RuntimeException—— 非受检异常 (Unchecked)

调用者可以不处理(编译不强制),适合编程错误(如参数校验失败)。

publicclassInsufficientBalanceExceptionextendsRuntimeException{privatedoubledeficit;// 额外信息:缺多少钱publicInsufficientBalanceException(Stringmessage,doubledeficit){super(message);this.deficit=deficit;}publicdoublegetDeficit(){returndeficit;}}

示例:用户注册年龄限制

// 1. 自定义异常类classInvalidAgeExceptionextendsException{publicInvalidAgeException(Stringmessage){super(message);}}// 2. 使用异常的方法publicclassUserService{publicvoidregister(Stringname,intage)throwsInvalidAgeException{if(age<18){thrownewInvalidAgeException("未满18岁,无法注册");}System.out.println(name+" 注册成功");}}// 3. 调用并处理publicclassMain{publicstaticvoidmain(String[]args){UserServiceservice=newUserService();try{service.register("小明",16);}catch(InvalidAgeExceptione){System.out.println("注册失败:"+e.getMessage());}}}

输出:

注册失败:未满18岁,无法注册

示例:自定义业务异常

// 自定义业务异常publicclassBizExceptionextendsRuntimeException{publicBizException(Stringmsg){super(msg);}}
publicclassDemo{publicstaticvoidbuy(intstock){if(stock<=0){thrownewBizException("库存不足,无法下单");}}publicstaticvoidmain(String[]args){try{buy(0);}catch(BizExceptione){System.out.println("业务提示:"+e.getMessage());}}}
业务提示:库存不足,无法下单

3)最佳实践

建议说明
提供多个构造方法至少提供无参、带 message、带 cause 的构造器
添加serialVersionUID如果异常可能被序列化(RMI、分布式),建议加上
类名以Exception结尾符合 Java 命名惯例,如MyBusinessException
区分 checked / unchecked可恢复的异常用Exception,编程错误用RuntimeException
携带额外信息通过自定义字段提供更丰富的错误上下文

自定义异常让你的代码更健壮、更易读、更容易调试

4)养成习惯:

  1. 标准格式统一:try-catch-finally 按规范写,不随意简写;
  2. try 最小化:只包裹危险代码,不扩大捕获范围;
  3. 不空 catch:必须打印日志/提示,禁止吃掉异常;
  4. finally 只释放资源:不写业务,不写 return;
  5. throw 提前校验:主动抛异常,提高系统容错性;
  6. 自定义异常统一业务错误:不用通用异常混淆业务;
  7. 异常信息完整:带堆栈、带提示、带错误码。

六、异常的实质

1. 面向对象层面

异常的实质是普通Java对象,全部实例化自Throwable及其子类

  1. 不管是系统自动抛出的空指针、数组越界,还是你手写throw手动抛出的自定义异常,本质都是堆内存里创建出来的对象;
  2. 这个对象内部封装了三类关键信息:
    • 异常描述信息:错误文字提示(getMessage());
    • 完整调用栈帧:哪一行代码、哪个方法、哪个类触发的错误;
    • 异常类型:用来被catch精准匹配区分。
  3. Java设计这套对象体系的目的:把错误信息、错误位置、错误类型封装成统一对象,统一传递、统一捕获、统一处理,替代C语言单纯返回错误码的简陋方案。

简单总结:异常 = 封装了完整错误信息的Throwable对象。

2.throw 代码层底层逻辑

1.执行 throw new 异常() 时,JVM 会创建一个异常对象(包含堆栈、类型、信息);
2.随后立即停止当前方法后续所有代码执行;
3.接着程序会一层一层往调用它的上层方法找,对比 catch 里写的异常类型,看能不能匹配上。
4.
1.找到匹配 catch:进入 catch 执行异常处理逻辑,处理完继续往后运行;
2.全程没有任何 catch 接住:JVM 在控制台打印完整红色异常堆栈,直接结束当前程序。
而异常本质是对象,throw 就是把这个封装好错误信息的对象向上传递给上层代码(抛给上层)。

配套简单示例

publicstaticvoidcheckAge(intage){// 参数校验,拦截非法数据if(age<0||age>150){// 手动构建异常对象并抛出thrownewIllegalArgumentException("年龄不合法,必须在 0-150 之间");}System.out.println("年龄校验通过");}

调用checkAge(-5)时,直接抛出异常,后面打印语句不会执行。

3、throw 的 JVM Native(C++) 底层逻辑(深层原理)

我们写的 throw 只是 Java 语法关键字,真正完成异常整套操作的不是 Java,是 JVM 底层用 C++ 写的本地 native 方法。
完整流程:

  1. Java 代码读到 throw 关键字,通知 JVM 暂停当前线程执行;
  2. JVM 调用内部C++函数,完成异常对象初始化,抓取当前线程所有栈帧信息;
  3. C++ 底层自动遍历当前线程的调用栈,挨个查找能匹配该异常的 catch 代码块;
  4. 平时打印报错信息的printStackTrace(),底层也是 native 方法,读取 C++ 保存的栈帧数据展示给用户;
  5. 流程中断、异常向上传递、类型匹配判断,全部由C++代码实现,Java 只负责书写语法。

一句话总结:Java 只是提供了 throw 书写语法,真正抛异常、检索catch、生成报错堆栈,全靠 JVM 底层 C++ native 代码运行。

4、自定义异常底层运行逻辑

自定义异常没有独立全新的底层机制,完全依托 Java 继承体系和JVM统一异常规则,分4点讲清楚:

  1. 硬性规则:必须继承Throwable
    只有直接/间接继承Throwable(包含 Exception、RuntimeException),JVM 才会把这个类识别为合法异常,支持抛出、捕获、生成堆栈信息。普通类无法使用 throw 抛出。
  2. catch 匹配只看类型,不看类名
    捕获异常时,JVM 通过类的继承关系判断是否匹配,和自定义类的名字无关。
  3. 父类决定异常分类
  • 直接继承Exception:受检异常,编译器强制要求要么 try-catch 捕获,要么方法加 throws 声明;
  • 继承RuntimeException:运行时异常,编译无强制校验,业务开发最常用。
  1. 构造方法必须写super(message)
    子类构造方法调用父类 Throwable 的构造,作用是把自定义错误提示交给父类底层保存,后续打印异常时才能正常展示提示文字。
5、自定义异常依赖的 Native 底层支撑

打开 Throwable 源码可以看到核心 native 方法:

publicclassThrowableimplementsSerializable{// 生成异常堆栈的核心方法,由C++本地方法实现privatenativeThrowablefillInStackTrace(intdummy);}
  1. 只要继承 Throwable,自定义异常会自动继承这个 native 底层能力,不用自己实现堆栈抓取、异常传递逻辑;
  2. 堆栈采集、异常抛出、栈帧遍历全部复用JVM统一的C++底层逻辑;
  3. 自定义异常仅做业务区分包装(用来分辨库存不足、用户名错误等业务场景),底层调度逻辑和系统自带异常完全共用一套native机制。

最终总结:自定义异常 = 业务场景分类包装 + 继承Throwable体系 + 复用JVM底层native整套异常调度机制。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/18 16:53:33

AI科技热点日报 | 2026年06月18日

文章目录AI科技热点日报 | 2026年06月18日&#x1f4cc; 今日摘要一、Transformer 之父 Noam Shazeer 跳槽 OpenAI,大模型架构战再升级事件概要来源 / Sources二、WAIC 2026 将于 7 月 17-20 日上海启幕,超 300 款 AI 产品全球首发事件概要来源 / Sources三、科创板第五套上市标…

作者头像 李华
网站建设 2026/6/18 16:50:34

学术研究图谱_academic-research-mapper

以下为本文档的中文说明该技能用于绘制任何技术或学术主题的研究领域图谱。它通过搜索arXiv、Semantic Scholar等学术数据库&#xff0c;系统性地收集和分析相关文献&#xff0c;识别研究趋势、关键论文、主要研究者和机构合作关系。该技能自动构建主题的知识结构图谱&#xff…

作者头像 李华
网站建设 2026/6/18 16:43:33

JavaScript 的异步管家:彻底搞懂 Promise 原型方法

&#x1f914; 为什么需要 Promise&#xff1f; 在 ES6 之前&#xff0c;处理异步操作&#xff08;如网络请求、定时器&#xff09;主要靠回调函数&#xff08;Callback&#xff09;。当逻辑复杂时&#xff0c;容易陷入“回调地狱”&#xff08;Callback Hell&#xff09;&…

作者头像 李华
网站建设 2026/6/18 16:39:57

【信息科学与工程学】【物理/化学和工程技术】汽车中的动力学

编号 类型 领域 问题 问题的数学分析【含几何/拓扑/介质/材料/边界分布/质量分布/密度分布/力学分布/应力分布/其他】 参数列表及边界范围及数值分析及泛函分析及矩阵分析 关联知识 1 纵向动力学 车辆直线加速与制动 给定车辆质量、发动机扭矩、传动比、滚动阻力、空…

作者头像 李华
网站建设 2026/6/18 16:33:28

洛雪音乐音源终极配置指南:如何免费获取全网无损音乐?

洛雪音乐音源终极配置指南&#xff1a;如何免费获取全网无损音乐&#xff1f; 【免费下载链接】lxmusic- lxmusic(洛雪音乐)全网最新最全音源 项目地址: https://gitcode.com/gh_mirrors/lx/lxmusic- 你是否厌倦了在各大音乐平台间来回切换&#xff0c;只为找到一首想听…

作者头像 李华
网站建设 2026/6/18 16:33:08

CentOS 如何安装并配置 Nginx

从零开始&#xff0c;在 CentOS 上装好 Nginx&#xff0c;配置好虚拟主机&#xff0c;十分钟上线。一、为什么选 Nginx&#xff1f; 如果你在 CentOS 上搭建 Web 服务&#xff0c;Nginx 几乎是首选。对比项NginxApache并发能力极强&#xff0c;事件驱动架构较弱&#xff0c;进程…

作者头像 李华