ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

第15章 异常处理

第15章 异常处理 第15章 异常处理程序运行中难免出错除数为零、数组越界、文件不存在……Java 用异常机制优雅地处理这些错误避免程序直接崩溃。本节学习异常体系与 try-catch-finally。一、什么是异常异常Exception是程序运行过程中出现的非正常情况。如果不处理程序会中断int[]arr{1,2,3};System.out.println(arr[5]);// 运行时报错ArrayIndexOutOfBoundsExceptionJava 默认会把异常信息打印到控制台红色错误堆栈然后程序终止。要学会读错误堆栈——第一行是异常类型原因下面的at行是调用栈指出哪一行出的错// 运行输出示例// Exception in thread main java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 3// at Test.main(Test.java:3)**为什么要用异常机制**如果没有它每个可能出错的地方都要写 if 判断 错误码返回代码会被错误处理淹没。异常把错误处理和正常逻辑分开正常逻辑写在 try 里错误处理集中在 catch 里。二、异常体系结构Throwable所有异常和错误的根类 ├── Error 严重错误程序无法处理如 OutOfMemoryError、StackOverflowError └── Exception 程序可以处理的异常 ├── RuntimeException 运行时异常编译不检查 │ ├── NullPointerException 空指针 │ ├── ArrayIndexOutOfBoundsException 数组越界 │ ├── ArithmeticException 算术异常除零 │ ├── ClassCastException 类型转换异常 │ └── NumberFormatException 数字格式异常 └── 检查型异常Checked 编译时就要求必须处理 ├── IOException IO 异常文件读写 ├── SQLException 数据库异常 └── ...关键概念运行时异常Unchecked编译器不强制处理一般是代码 bug检查型异常Checked编译器强制必须处理try-catch 或 throws 声明否则编译失败一句话区分运行时异常是代码写错了检查型异常是外部环境出问题了。空指针、越界、除零都是代码 bug文件不存在、网络断了、数据库连不上代码再对也可能发生。// 运行时异常编译不报错运行才崩intr10/0;// 编译通过// 检查型异常编译就不让过不处理会编译失败// FileInputStream in new FileInputStream(a.txt); // 编译错误未处理的 IOException三、捕获异常try-catchtry{// 可能出错的代码intresult10/0;}catch(ArithmeticExceptione){System.out.println(除数为零e.getMessage());}try放置可能出错的代码catch捕获并处理异常e.getMessage()获取错误信息注意try 里一旦出错出错点之后的语句就不再执行直接跳到 catchtry{System.out.println(第 1 行);intr10/0;// 这里出错System.out.println(第 2 行);// 不会执行}catch(ArithmeticExceptione){System.out.println(捕获到异常);}// 输出// 第 1 行// 捕获到异常异常对象常用方法try{int[]arr{1,2,3};System.out.println(arr[5]);}catch(ArrayIndexOutOfBoundsExceptione){System.out.println(e.getMessage());// Index 5 out of bounds for length 3e.printStackTrace();// 打印完整调用栈开发期最常用}多分支 catch按类型逐个处理try{int[]arrnewint[5];arr[10]100;}catch(ArithmeticExceptione){System.out.println(算术异常);}catch(ArrayIndexOutOfBoundsExceptione){System.out.println(数组越界);// 会走到这里}多个 catch 时子类异常要写在父类前面否则子类永远捕获不到编译报错。多个异常一起捕获JDK 8}catch(ArithmeticException|NullPointerExceptione){System.out.println(算术或空指针);}四、finally无论如何都要执行try{// ...}catch(Exceptione){// ...}finally{// 无论 try 是否报错、catch 是否处理finally 一定执行}典型用途释放资源关闭文件、数据库连接、网络连接FileInputStreaminnull;try{innewFileInputStream(a.txt);// 读取文件...}catch(IOExceptione){System.out.println(读取失败);}finally{if(in!null){try{in.close();}catch(IOExceptione){}}}注意即使 try 或 catch 中有returnfinally 仍然会执行在 return 之前执行publicstaticvoidtest(){try{System.out.println(try 执行);return;}finally{System.out.println(finally 执行);// 有 return 也会执行}}test();// try 执行// finally 执行finally 和 return 之间的微妙关系比如 finally 里写 return 会覆盖返回值见下文扩展知识。五、try-with-resourcesJDK 8手动关闭资源太啰嗦从 JDK 8 起支持自动关闭try(FileInputStreaminnewFileInputStream(a.txt)){// 使用 in 读取文件}catch(IOExceptione){System.out.println(读取失败);}// 不用写 finallyin 会自动关闭要求资源实现 AutoCloseable 接口多个资源一起管理JDK 8try(FileInputStreaminnewFileInputStream(a.txt);FileOutputStreamoutnewFileOutputStream(b.txt)){// 边读边写复制文件}catch(IOExceptione){System.out.println(文件操作失败);}// in、out 都会自动关闭不只是 JDK 自带的类任何实现AutoCloseable接口的类都能享受自动关闭classMyResourceimplementsAutoCloseable{Overridepublicvoidclose(){System.out.println(资源已关闭);}}try(MyResourcernewMyResource()){System.out.println(正在使用资源);}// 正在使用资源// 资源已关闭异常处理的整套语法——try-catch、finally、try-with-resources、throw/throws——从 JDK 8 到 JDK 11、JDK 17 写法完全一致三个版本里都能直接照抄使用。六、抛出异常throw 与 throwsthrow主动抛出异常publicvoidsetAge(intage){if(age0||age150){thrownewIllegalArgumentException(年龄不合法age);}this.ageage;}throws声明方法可能抛出的异常publicvoidreadFile()throwsIOException{FileInputStreaminnewFileInputStream(a.txt);// 不 try-catch声明抛出去}调用方要么 catch要么继续 throwspublicvoidhandle(){try{readFile();}catch(IOExceptione){System.out.println(文件操作失败);}}throws 和 throw 的区别throws写在方法声明上声明这个方法可能抛出哪些异常throw写在方法体内实际抛出一个异常对象// 实战捕获主动抛出的异常try{thrownewIllegalArgumentException(年龄不合法-5);}catch(IllegalArgumentExceptione){System.out.println(e.getMessage());// 年龄不合法-5}运行时异常可以不用 throws 声明写了更清晰不写也不报错检查型异常不声明就编译不过。七、扩展知识7.1 异常体系树细看Error 与 ExceptionError是 JVM 级的问题程序本身救不回来比如无限递归把栈空间耗尽StackOverflowError、死循环 new 对象把堆内存占满OutOfMemoryError。遇到 Error 基本只能修代码try-catch 也拦不住。Exception之下运行时异常家族是最常遇到的都是代码 bug异常常见触发场景NullPointerException调用 null 对象的方法或属性ArrayIndexOutOfBoundsException数组下标越界ArithmeticException整数除零ClassCastException强制类型转换失败NumberFormatException字符串转数字失败如 “abc” 转 int7.2 受检异常 vs 运行时异常对比项检查型Checked运行时Unchecked编译检查必须 try-catch 或 throws否则编译失败不强制编译通过常见类型IOException、SQLException、ClassNotFoundExceptionNullPointerException、ArrayIndexOutOfBoundsException、ArithmeticException产生原因外部环境文件、网络、数据库代码 bug逻辑错误处理策略必须处理或向上声明可以不管但最好防御式编程先判 null 等怎么判断一个异常是哪种编译一下就知道——不处理就编译报错的是检查型编译通过、运行才崩的是运行时。7.3 finally 与 return 的关系先执行 finally再真正返回publicstaticinttest(){try{return1;}finally{System.out.println(finally 先执行);}}System.out.println(test());// finally 先执行// 1坑finally 里写 return 会覆盖 try 的 returnpublicstaticinttest(){try{return1;}finally{return2;// 覆盖了 1}}System.out.println(test());// 2规则finally 只做收尾工作关资源、清状态永远不要写 return——覆盖返回值会让调用方拿到意料之外的结果极难排查。例外System.exit 直接结束 JVMfinally 不会执行try{System.exit(0);// 整个程序直接退出}finally{System.out.println(不会执行);}7.4 自定义异常业务异常的规范写法实际开发中常自定义异常表达业务错误// 推荐继承 RuntimeException调用方不 catch 也能编译使用灵活// 命名规范业务名 ExceptionclassMoneyNotEnoughExceptionextendsRuntimeException{publicMoneyNotEnoughException(Stringmessage){super(message);}publicMoneyNotEnoughException(Stringmessage,Throwablecause){super(message,cause);// 保留原始异常链方便排查根因}}publicvoidpay(doubleamount,doublebalance){if(amountbalance){thrownewMoneyNotEnoughException(余额不足还差 (amount-balance) 元);}System.out.println(支付成功);}try{pay(100,30);}catch(MoneyNotEnoughExceptione){System.out.println(e.getMessage());// 余额不足还差 70.0 元}更规范的做法是先定义父级业务异常再细分——调用方 catch 一个父类就能接住所有同类错误// 先定义账户业务的总异常classAccountExceptionextendsRuntimeException{publicAccountException(Stringmessage){super(message);}}classMoneyNotEnoughExceptionextendsAccountException{publicMoneyNotEnoughException(Stringmessage){super(message);}}classAccountLockedExceptionextendsAccountException{publicAccountLockedException(Stringmessage){super(message);}}// 调用方只需 catch 一个 AccountException就能接住所有账户业务错误try{pay(100,30);}catch(AccountExceptione){System.out.println(账户业务出错e.getMessage());}命名规范自定义异常类名以Exception结尾。八、异常处理最佳实践不要捕获不处理的空 catchcatch (Exception e) { }吞掉异常会让 bug 极难排查尽量捕获具体的异常类型不要一律 catch (Exception)异常要记录日志打印 e.printStackTrace() 或写入日志文件不要用异常控制业务流程如用 NumberFormatException 判断是否为数字正常流程用 if 判断资源及时关闭优先 try-with-resources在合适的层级处理异常底层方法抛出去由上层统一处理比如给用户友好提示finally 中不要写 returncatch 里吞掉异常后也别假装处理成功九、易错点易错 1捕获范围过宽吞掉细节// ❌ 一律 catch Exception具体错误类型和原因全被吞掉try{intr10/0;}catch(Exceptione){System.out.println(出错了);}// ✅ 捕获具体类型保留信息try{intr10/0;}catch(ArithmeticExceptione){System.out.println(除数为零e.getMessage());}易错 2空 catch捕获了却什么都不做// ❌ 异常被静默吞掉问题无迹可寻// catch (IOException e) { }// ✅ 至少打印日志让问题可见catch(IOExceptione){e.printStackTrace();}易错 3finally 里 return 覆盖 try 的 return// ❌ 调用方永远拿到 2try 里的 1 白算了publicstaticintget(){try{return1;}finally{return2;}}// ✅ finally 只做收尾不写 returnpublicstaticintget(){try{return1;}finally{System.out.println(收尾工作);}}易错 4资源用完忘记关闭// ❌ 用完不关文件句柄/数据库连接泄漏程序跑久了资源耗尽publicvoidread()throwsIOException{FileInputStreaminnewFileInputStream(a.txt);// 使用 in ... 忘了 close()}// ✅ try-with-resources 自动关闭JDK 8try(FileInputStreaminnewFileInputStream(a.txt)){// 使用 in ...}易错 5检查型异常不处理// ❌ 编译失败IOException 是检查型异常必须处理// public void read() {// new FileInputStream(a.txt);// }// ✅ 二选一自己 try-catch或 throws 抛给调用方publicvoidread()throwsIOException{newFileInputStream(a.txt);}十、小结Throwable 之下分 Error不可处理和 Exception可处理运行时异常是代码 bug不强制处理检查型异常必须处理try-catch 或 throws否则编译不过try-catch 捕获、finally 收尾先于 return 执行但别在 finally 里 return、try-with-resources 自动关闭资源JDK 8throw 主动抛出throws 声明抛出业务错误可以自定义异常继承 RuntimeException命名以 Exception 结尾可先建父级业务异常最佳实践捕获具体异常、不空 catch、记录日志、资源必关下一节学习集合框架List 与 Set。下一篇第16章 集合框架List 与 Set
返回列表