Java 异常机制与处理

发布时间:2026/9/27 9:23:23
Java 异常机制与处理 文章目录1.了解异常1.1 初步认识异常了解 Exception 家族2. 处理异常2.1 防御式编程2.2 抛出异常Throwable.getCause()方法使用2.3 捕获异常2.3.1 异常声明 throws2.3.2 try-catch 捕获并处理2.3.3finally2.4 异常的处理流程3.自定义异常类1.了解异常1.1 初步认识异常了解 Exception 家族在任何代码中错误以及意外是不可避免的——除数为零文件路径不正确非法输入…既然知道存在这种情况我们就需要提前预防。因此 Java 的异常处理机制就应运而生。通过抛出异常throw、声明异常throws和捕获异常try-catch实现从错误检测到错误恢复的完整流程。常见异常System.out.println(10/0);//ArithmeticException 算数异常int[]arr{1,2,3,4,5};System.out.println(arr[10]);//ArrayIndexOutOfBoundsException 数组越界异常intarrnull;System.out.println(arr.length);//NullPointerException 空指针异常...事实上异常又分为编译时异常受检异常CheckedException)运行时异常非受检异常UncheckedException。编译时异常1.编译阶段就会被检查必须显式处理否则编译不通过。2.表示“可预料错误”如“文件不存在”、“网络中断”、“数据库连接失败”…特点编译器要求要么try-catch捕获要么在方法上throws声明运行时异常1.运行时才可能发生编译器不会强制要求处理。2.表示“程序包逻辑错误或无法预料的问题”。特点通常是开发者的逻辑错误导致的不建议用 try-catch 去包而是应修正逻辑。这里我们先了解下他们的继承关系如下图我们可以了解到Throwable是异常体系的根他继承自Object。Throwable有两个体系Error和ExceptionError表示严重的错误程序对此一般无能为力错误不是异常而是脱离程序控制的问题 egOutOfMemoryError内存耗尽NoClassDefFoundError无法加载某个 ClassStackOverflowError栈溢出Exception则是运行时的错误它可以被捕获并处理。某些异常是应用程序逻辑处理的一部分应该捕获并处理。egNumberFormatException数值类型的格式错误FileNotFoundException未找到文件SocketException读取网络失败2. 处理异常2.1 防御式编程先了解一下什么是防御式编程以便让我们更好的了解如何处理异常防御式编程Defensive Programming是一种编程思想指在写代码时假设“可能出错”并提前在程序中检测、预防、或妥善处理这些潜在错误以防止程序在异常情况下崩溃或产生错误结果简单理解 就像司机开车时随时准备刹车而不是等撞上去再想办法我们可以举一个例子进行对比普通写法无防御importjava.io.*;publicclassExample1{publicstaticvoidmain(String[]args)throwsIOException{FileReaderfrnewFileReader(data.txt);// 假设文件存在BufferedReaderbrnewBufferedReader(fr);System.out.println(第一行内容是br.readLine());br.close();}}防御式编程importjava.io.*;publicclassExample2{publicstaticvoidmain(String[]args){StringfilePathdata.txt;FilefilenewFile(filePath);// ① 防御检查文件是否存在if(!file.exists()){System.err.println(错误文件不存在 → filePath);return;}// ② 防御检查是否是普通文件防止是目录if(!file.isFile()){System.err.println(错误filePath 不是一个有效文件);return;}// ③ 使用 try-with-resources自动关闭流try(BufferedReaderbrnewBufferedReader(newFileReader(file))){StringfirstLinebr.readLine();// ④ 防御检查文件是否为空if(firstLinenull){System.err.println(提示文件为空没有任何内容。);return;}System.out.println(第一行内容是firstLine);}catch(IOExceptione){// ⑤ 防御捕获所有IO错误并给出友好提示System.err.println(读取文件时出错e.getMessage());}}}总结一句话就是 防御式编程就是“预先假设问题会发生并设计好应对措施”而不是“等异常爆炸再修”。因此我们开始踏入 Java 的异常使用啦先初步了解五个异常常用关键字关键字作用位置举例throw抛出一个具体的异常对象方法体内throw new Exception(出错了);throws声明该方法可能抛出哪些异常方法声明上public void readFile() throws IOExceptiontry可能出错的代码块方法体内try { riskyCode(); }catch捕获并处理异常紧跟 try 后catch(Exception e) { ... }finally无论是否异常都执行可选finally { cleanUp(); }2.2 抛出异常追踪本源当发生错误时比如用户输入非法字符那么就可以抛出异常。抛出异常分为两步我们可以参考Integer.parseInt()方法1.创建Exception的实例2.用throw语句抛出eg:voidexample(Strings){if(snull){NullPointerExceptionenewNullPointerException();throwe;//通常写为一条语句用匿名对象//throw new NullPointerException();}}但是如果一个方法捕获了某个异常后又在catch子句中抛出新的异常就相当于把抛出的异常类型“转型”了:voidexample1(Strings){try{example2();}catch(NullPointerExceptione){thrownewIllegalArgumentException();}}voidexample2(Strings){if(snull){thrownewNullPointerException();}}这段代码展示的就是将NullPointerException异常转为了IllegalArgumentException最终展示报错结果只有IllegalArgumentException我们补为 static 方法放在 main 方法中打印出异常栈看看publicclassTestDemo3{publicstaticvoidmain(String[]args){try{example1();}catch(Exceptione){e.printStackTrace();}}staticvoidexample1(){try{example2();}catch(NullPointerExceptione){thrownewIllegalArgumentException();}}staticvoidexample2(){thrownewNullPointerException();}}打印出的异常栈信息即为java.lang.IllegalArgumentExceptionatdemo2.TestDemo3.example1(TestDemo3.java:23)atdemo2.TestDemo3.main(TestDemo3.java:13)说明了新的异常丢失了原始异常信息即看不到了原本的NullPointerException的信息了。所以为了追踪完整的异常栈我们在构造异常的时候就把原始的Exception实例传进去(将 e 对象进行异常包装/异常链)新的Exception就可持有原来的Exception信息。代码如下publicclassTestDemo3{publicstaticvoidmain(String[]args){try{example1();}catch(Exceptione){e.printStackTrace();}}staticvoidexample1(){try{example2();}catch(NullPointerExceptione){thrownewIllegalArgumentException(e);}}staticvoidexample2(){thrownewNullPointerException();}}这样我们就保留下来最开始抛出的异常类型了以方便我们直接溯源根源java.lang.IllegalArgumentException:java.lang.NullPointerExceptionatdemo2.TestDemo3.example1(TestDemo3.java:23)atdemo2.TestDemo3.main(TestDemo3.java:13)Causedby:java.lang.NullPointerExceptionatdemo2.TestDemo3.example2(TestDemo3.java:28)atdemo2.TestDemo3.example1(TestDemo3.java:21)在代码中获取原始异常可以使用Throwable.getCause()方法。若返回 null说明已经是“根异常”了。Throwable.getCause()方法使用Throwablecausee.getCause();-返回触发当前异常的“原始异常”-如果返回 null说明当前异常没有再包裹别的异常是“根异常”。基本使用try{someMethod();}catch(Exceptione){Throwablecausee.getCause();System.out.println(当前异常是e);System.out.println(原始原因是cause);}实际开发中常用遍历整条异常链若异常被包装多次可以使用循环一直调用 getCause()staticvoidprintChain(Throwablet){while(t!null){System.out.println(异常: t);tt.getCause();// ⭐关键向上查找}}2.3 捕获异常2.3.1 异常声明 throwsthrows 处在方法声明参数列表之后当方法中抛出编译时异常用户不想处理该异常此时就可以借助 throws 将异常抛出给方法的调用者来处理。即当前方法不处理异常提醒方法的调用者处理异常。修饰词 返回值类型 方法名(参数列表)throws异常类型1,异常类型2...{}我们举个例子看看 throws 用法publicvoidOpenConfig(Stringfilename)throwsFileNotFoundException{if(filename.equals(config.ini)){thrownewFileNotFountException(配置文件名字不对);}}语法点注意1.throws 必须更在方法的参数列表之后2.声明的异常必须是 Exception 或者 Exception 的子类3.方法内部如果抛出多个异常throws 之后必须跟多个异常类型之间用逗号隔开如果抛出多个异常型具有父子关系直接声明父类即可。4.调用声明抛出异常的方法时调用者必须对该异常进行处理或者继续使用 throws 抛出2.3.2 try-catch 捕获并处理throws 对异常并没有真正处理而是将异常报告给抛出异常方法的调用者由调用者处理。如果真正要对异常进行处理就需要 try-catch。try{// 将可能出现异常的代码放在这里}catch(要捕获的异常类型 e){// 如果try中的代码抛出异常了此处catch捕获时异常类型与try中抛出的异常类型一致时或者是try中抛出异常的基类//时就会被捕获到// 对异常就可以正常处理处理完成后跳出try-catch结构继续执行后序代码}catch(异常类型 e){// 对异常进行处理}finally{// 此处代码一定会被执行到}1.try 块内抛出异常位置之后的代码将不会被执行2.如果抛出异常类型与 catch 时异常类型不匹配即异常不会被成功捕获也就不会被处理继续往外抛知道 JVM 收到后中断程序----异常是按照类型来捕获的3.try 中可能会抛出多个不同的异常对象则必须使用多个 catch 来捕获----多种异常多次捕获2.3.3finally像在代码中当程序需要中断并且需要我们关闭资源避免浪费的时候finally 很好的帮我们解决了这个问题。语法格式try{// 可能会发生异常的代码}catch(异常类型 e){// 对捕获到的异常进行处理}finally{// 此处的语句无论是否发生异常都会被执行到}// 如果没有抛出异常或者异常被捕获处理了这里的代码也会执行举个范例publicclassTestFinally{publicstaticintgetData(){Scannerscnull;try{scnewScanner(System.in);intdatasc.nextInt();returndata;}catch(InputMismatchExceptione){e.printStackTrace();}finally{System.out.println(finally中代码);}System.out.println(try-catch-finally之后代码);if(null!sc){sc.close();}return0;}publicstaticvoidmain(String[]args){intdatagetData();System.out.println(data);}}// 正常输入时程序运行结果100finally中代码100上述程序如果正常输入成功接收输入后程序就返回了try-catch-finally 之后的代码根本就没有被释放造成资源泄漏。注意finally 中的代码一定会执行的一般在 finally 中进行一些资源清理的扫尾工作。// 下面程序输出什么publicstaticvoidmain(String[]args){System.out.println(func());}publicstaticintfunc(){try{return10;}finally{return20;}}A:10B:20C:30D:编译失败答案解析java 规则1.try 中遇到 return 时先把返回值记录下来但不立即返回2.finally 会在 return 执行前运行3.如果 finally 中也写了 return他会覆盖 try 的返回值因此建议不在 finally 中写 return被编译器当作一个警告【面试题】throw 和 throws 的区别finally中的语句一定会执行吗throws 用于方法声明处表示该方法可能抛出某类异常让调用者处理。throw 用于方法内部创建并抛出一个异常对象中断方法执行。throws 是声明throw 是动作throws 后跟异常类型throw 后跟异常对象。2.几乎一定会执行但有两个例外JVM 提前退出eg: 调用System.exit(0);JVM 直接停止不执行 finallyfinally 执行前被线程杀死eg: (1)线程再 finally 前被 stop() (2)程序崩溃导致线程终止2.4 异常的处理流程我们先了解一下“调用栈”方法之间是存在互相调用关系的这种调用关系我们可以用“调用栈”来描述在 JVM 中有一块内存空间称为“虚拟机栈”专门存储方法之间的调用关系当代码中出现异常的时候我们就可以使用e.printStackTrace();的方式查看出现异常代码的调用栈你main 方法→ 去办业务办事人员让你先去窗口 A调用 methodA窗口 A 又让你先去窗口 B调用 methodB窗口 B 又让你去窗口 C调用 methodC你去每一个窗口都会把“你当前在哪一层”记录下来。这就是——调用栈Call Stack然后呢了解调用栈之后我们能更好的理解异常处理流程了1.代码运行时发生错误 - JVM 创建异常对象2.JVM 将异常抛出到当前方法throw3.JVM 查找匹配的 catch当前方法内查找4.当前方法没有处理 - 异常向上抛给调用者throws5.异常层层向上知道被某层 catch 处理6.如果最终无人处理 - JVM 答应异常栈 - 程序终止如果有匹配的 catch - catch 执行7.finally 总会执行8.若 catch 中再次 throw - 异常类型被“转换”继续向上抛3.自定义异常类当我们在代码中需要抛出异常时尽量使用 JDK 已定义的异常类型。例如参数检查不合法应该抛出IllegalArgumentExceptionstaticvoidexample1(intage){if(age0){thrownewIllegalArgumentException();}}常见做法是自定义一个BaseException作为“根异常”然后派生出各种业务类型的异常。BaseException需要从一个适合的Exception派生通常建议从Runtime Exception派生publicclassBaseExceptionextendsRuntimeException{}也因此其他业务类型的异常就可以从BaseException派生publicclassUserNotFoundExceptionextendsBaseException{}publicclassLoginFailedExceptionextendsBaseException{}...自定义的BaseException应该提供多个构造方法publicclassBaseExceptionextendsRuntimeException{publicBaseException(){super();}publicBaseException(Stringmessage,Throwablecause){super(message,cause);}publicBaseException(Stringmessage){super(message);}publicBaseException(Throwablecause){super(cause);}}注意事项自定义异常通常会继承自 Exception 或者 RuntimeException继承自 Exception 的异常默认是受查异常继承自 RuntimeException 的异常默认是非受查异常还需注意受查异常和非受查异常的应用区别…自定义的BaseException应该提供多个构造方法 java public class BaseException extends RuntimeException { public BaseException() { super(); } public BaseException(String message, Throwable cause) { super(message, cause); } public BaseException(String message) { super(message); } public BaseException(Throwable cause) { super(cause); } }注意事项自定义异常通常会继承自 Exception 或者 RuntimeException继承自 Exception 的异常默认是受查异常继承自 RuntimeException 的异常默认是非受查异常还需注意受查异常和非受查异常的应用区别以上是我关于Java的笔记分享感谢你读到这里这也是我学习路上的一个小小记录。希望以后回头看时能看到自己的成长~