大家在写 Java 代码时,是不是经常被各种红色报错搞得头大? 空指针、数组越界、除零错误…… 这些都是程序里的异常。 异常处理是 Java 开发最基础、最常用、面试最高频的知识点之一。 今天这篇文章,我就带大家从 0 到 1 彻底搞懂 Java异常:概念、体系、处理、流程、自定义异常,一篇全搞定!
一、什么是异常?
简单一句话:
程序运行过程中发生的不正常行为,就叫异常。
就像人会生病一样,代码写得再严谨,也可能因为数据错误、网络问题、资源不足而出问题。
Java 中常见的异常我们几乎都遇到过:
// 1. 算术异常:除零错误System.out.println(10/0);// 2. 数组越界异常int[]arr={1,2,3};System.out.println(arr[100]);// 3. 空指针异常int[]arr=null;System.out.println(arr.length);每种异常,Java 都用一个异常类来描述,方便我们定位、处理。
二、Java 异常体系结构
Java 异常有一套非常清晰的继承体系,顶层父类是 Throwable。
Throwable├─Error(错误:JVM严重问题,无法处理) └─Exception(异常:可捕获处理,开发重点)- Throwable
所有异常、错误的顶级父类。 - Error(错误)
JVM 自己都解决不了的严重问题,程序员无法通过 try-catch 处理。
典型代表:
StackOverflowError:栈溢出OutOfMemoryError:内存溢出遇到 Error,只能改代码逻辑,没有别的办法。
3. Exception(异常)
我们平时说的 “异常”,就是它!
可以通过代码捕获处理,让程序继续运行。
三、异常的两大分类
按发生时机,异常分为两大类:编译时异常 和 运行时异常。
- 编译时异常(受检异常 Checked Exception)
编译期间就报错,不处理无法运行
必须显式捕获 或 声明抛出
例子:FileNotFoundException、IOException、CloneNotSupportedException// 编译直接报错:必须处理 CloneNotSupportedException@OverridepublicPersonclone(){return(Person)super.clone();}- 运行时异常(非受检异常 Unchecked Exception)
RuntimeException 及其子类
编译不报错,运行时才会触发
例子:NullPointerException、ArrayIndexOutOfBoundsException、ArithmeticException小提醒:
拼写错误、语法错误不是异常,是编译错误!
四、异常处理核心思想
处理异常有两种编程思想:
1.(事前检查) 先判断,再操作。正常代码和错误代码混在一起,结构混乱。
2.(先操作,出错再处理) 先执行业务代码,遇到异常再捕获处理。 正常流程与异常流程分离,代码更清晰。
Java 异常处理的核心思想就是:(先操作,出错再处理)
Java 异常 5 大关键字:throw、try、catch、finally、throws
五、异常的抛出:throw
主动手动抛出异常,把错误信息告诉调用者。
thrownew异常类("异常提示信息");示例:获取数组元素并校验publicstaticintgetElement(int[]array,intindex){if(array==null){thrownewNullPointerException("数组不能为 null");}if(index<0||index>=array.length){thrownewArrayIndexOutOfBoundsException("数组下标越界");}returnarray[index];}throw 注意事项
- 必须写在方法内部
- 只能抛出 Exception 或其子类对象
- 运行时异常可不用处理,编译时异常必须处理
- 异常抛出后,后续代码不再执行
六、异常的捕获与处理
- 异常声明:throws
方法自己不处理异常,甩锅给调用者。
修饰符 返回值类型 方法名(参数列表)throws异常1,异常2...{}示例
publicvoidopenConfig(Stringfilename)throwsFileNotFoundException{if(!filename.equals("config.ini")){thrownewFileNotFoundException("配置文件名称错误");}}throws 要点
- 跟在方法参数列表之后
- 可声明多个异常,有父子关系只需要写父类
- 调用者必须处理,或继续 throws
真正捕获:try-catch-finally
自己处理异常,让程序不崩溃。
try{// 可能出现异常的代码}catch(异常类型 e){// 捕获并处理异常}finally{// 无论是否异常,一定会执行}示例
publicstaticvoidmain(String[]args){try{int[]arr={1,2,3};System.out.println(arr[100]);}catch(ArrayIndexOutOfBoundsExceptione){e.printStackTrace();// 打印完整异常栈}System.out.println("程序继续执行~");}catch 捕获规则
按异常类型匹配捕获
多个异常:多个 catch 或 使用 并且 合并
子类异常在前,父类异常在后,否则编译报错
不推荐直接 catch (Exception),会吞掉异常
必须执行:finally
无论是否异常、是否 return,finally 一定执行!
publicstaticintfunc(){try{return10;}finally{return20;}}// 输出结果:20
开发规范:绝对不要在 finally 里写 return!
七、异常处理流程
1.异常不是停在当前方法,而是向上传递:
2.本方法出现异常 → 寻找本层 catch
3.没有匹配 catch → 抛给调用方法
4.一直到 main 方法都没处理 → 交给 JVM → 程序终止
八、自定义异常
Java 内置异常不能满足所有业务场景,我们可以自己定义异常。
自定义步骤
继承 Exception(受检)或 RuntimeException(非受检)
实现带 String 参数的构造方法
// 受检异常classUserNameExceptionextendsException{publicUserNameException(Stringmessage){super(message);}}// 非受检异常classPasswordExceptionextendsRuntimeException{publicPasswordException(Stringmessage){super(message);}}使用示例
publicstaticvoidlogin(StringuserName,Stringpassword)throwsUserNameException{if(!"admin".equals(userName)){thrownewUserNameException("用户名错误");}System.out.println("登录成功");}核心速记
throw: 方法内,手动抛一个异常
throws: 方法上,声明异常类型
try: 放可能异常的代码
catch: 捕获处理
finally: 一定执行,关闭资源
throw vs throws的比较
throw 方法内抛对象;throws 方法声明异常。
总结
异常体系:Throwable → Error / Exception
两类异常:编译时(必须处理)、运行时(可不处理)
处理流程:throw 抛出 → throws 声明 → try-catch-finally 捕获
自定义异常:继承 Exception/RuntimeException 即可