软件编程语言琳琅满目,Java、C/C++、.Net、Node.js、Python、JavaScript、PHP、Rust、Go,适用范围、哲学流派也是百花齐放,客户端、前端、后端、移动端,脚本式、面向过程/对象、函数式编程,这些词藻甩出来瞬间刷屏,就此打住。我们不妨来关注下这些语言背后的 "万变不离其宗"之术吧,无论什么语言,异常处理无疑都是重中之重,本篇我们就来聊聊Java中的异常处理。
所谓优良程序,我认为其编程过程,少说,60%以上都是在和异常战斗,正所谓,“Bug千千万,测试第一条,异常不重视,岂止两行泪”。
1. 异常处理,老夫就是一把梭
在写Java代码时,有些代码是编译器要你强行捕获处理的,如下:
正例:
try { String ip = "某ip"; int port = 8080; // 通过socket,连接网络中的某个节点 new Socket().connect(new InetSocketAddress(ip, port));} catch (Exception e) { // 一把梭,出错打印,爱咋咋滴 e.printStackTrace();}
反例:
String ip = "某ip";int port = 8080;try { // 通过socket,连接网络中的某个节点 new Socket().connect(new InetSocketAddress(ip, port));} catch (SocketException e) { //日志记录,同时输出异常堆栈 logger.log("网络连接异常 目标节点[ip={} port={}], 已连接或连接已关闭", ip, port, e); /*陷入深沉的思考中... *1.这个异常在当前语境下会造成什么影响 *2.抛不抛出,不抛出我要做什么处理,如果抛出,除了记录日志,我还应该做些什么 *3.虽然exception被我catch到了,但有没有可能遗漏其它异常 *4.等等... */} catch (Exception e) { logger.log("发生了不可预计的网络连接异常 目标节点[ip={} port={}]", ip, port, e); /*陷入深沉的思考中 again... *重复以上思考过程... *在当前场景中,我认为网络连接异常本就是个checked exception,也就是说, *我要让调用我的人必须知道这点,由他决定怎么处理,兄弟对不住,我这把锅甩出去了 *throw e; *又或者,根据业内最新思潮,万错皆可视为RuntimeException, * 不过事没完,接下来我得在当前方法的throws子句中显式声明这些异常,好让别人知道... throw new RuntimeException(e);}//吾日三省吾身...//单元测试搞一搞?//覆盖度测试搞一搞?//代码有没有重复,是不是优雅,重构搞一搞?
2. JDK基础运行时异常,轻松捕获
Java开发的基本类库JDK定义了一些开箱即用的运行时异常(RuntimeException),比如NullPointerException、IndexOutOfBoundsException等,这些异常不应该通过try-catch捕获来处理,而应该通过主动预检查来处理,如下:
正例:
Biz biz = null;try { biz.run();} catch (NullPointerException e) { //轻松捕获臭名昭著的空指针异常,编程我最行!}int[] arrays = new int[]{1, 2, 3};try {int a = arrays[4];} catch (IndexOutOfBoundsException e) { //轻松捕获臭名昭著的数组越界异常,编程我最行!}
反例:
Biz biz = null;if (biz == null) { return;}biz.run();int[] array = new int[] {};if (array == null || array.length == 0) { return;}array[0] = 1;
3. 我也不知道哪会出错,try-catch罩起来,万无一失
catch时要分清,哪些代码不可能出错,哪些代码可能出错,不可能出错的不用try-catch(对新手来说,这点其实比较难做到),对于可能出错的代码进行try-catch,并尽可能针对异常分类处理,示例代码参考上文第1.条 ,此外,举两个栗子,如下:
- 包括且不限于 语言层面的赋值语句、简单的getter/setter调用,根本不可能报错,不用try-catch
- 某些第三方api/类库文档有时候会写某某方法抛出啥异常,你难道相信它永远按文档那样工作? 所以,建议try-catch(除非你是大牛钻进去看透代码,版本变化了你还得跟着)
最后记住,用try-catch包裹的代码是有性能开销的
4. 编译器老是报错,烦的一批,try-catch罩起来,世界安静了
在Java中,对于checked异常,编译器会提示你进行try-catch,编译器那么体贴,你一定要好好处理啊,如下:
![a0c3baa0a96e2beb16e100a30c8d0763.png](https://i-blog.csdnimg.cn/blog_migrate/4b04f273a04740f9fa5a04ef84a4b8e5.jpeg)
编译器报错高清无码图
发红了,赶紧try-catch起来,压压惊, 如下:
正例:
try { Thread.sleep(休眠毫秒数);} catch (InterruptedException e) { e.printStackTrace();}
反例:
try { Thread.sleep(休眠毫秒数);} catch (InterruptedException e) { logger.warn("线程{} 发生了中断", Thread.currentThread(), e); //传递中断信号(什么鬼?) Thread.currentThread().interrupt();}
5. 用异常来控制程序流程,神乎其技
异常就是异常,它代表程序的意外情况,不要仗着try-catch能捕获异常,用这个机制来处理正常的程序流程,如下:
正例:
public static void check(String param) { if (param == null) throw new ParamExcetpion("参数为空!"); if (param.indexOf("敏感词") != -1) throw new SensitiveWordException("发现敏感词");}public static void handleParamError() { System.out.println("执行参数为空后的业务逻辑");}public static void handleSensitiveWord() { System.out.println("执行发现敏感词后的业务逻辑");}public static void main(String[] args) { String param = null; try { check(param); } catch (ParamExcetpion e) { handleParamError(); } catch (SensitiveWordException e) { handleSensitiveWord(); }}
反例:
String param = "未知值";if (param == null) { handleNull(); return;}if (param.indexOf("敏感词") != -1) { handleSensitiveWord();}
6.finally中别忘记资源对象的关闭和释放,我知道啊
finally块必须对资源型对象进行关闭,以防泄漏,在这个过程中,针对某个特定资源对象的关闭,有任何异常都要做try-catch,防止close意外被miss掉,如下:
正例:
FileInputStream in = null;FileOutputStream out = null;try { in = new FileInputStream("in.txt"); out = new FileOutputStream("out.txt");} catch (Exception e) { e.printStackTrace();} finally { try { //妥妥关闭in和out in.close(); out.close(); } catch (IOException e) { e.printStackTrace(); }}
反例:
FileInputStream in = null;FileOutputStream out = null;try { in = new FileInputStream("in.txt"); out = new FileOutputStream("out.txt");} catch (Exception e) { // log略 throw new RuntimeException("文件读写错误", e);} finally { if (in != null) { try { in.close(); } catch (Exception e) { // 主动吞咽异常 log略 } } if (out != null) { try { out.close(); } catch (Exception e) { // 主动吞咽异常 log略 } }}
7. try-finally终于形成肌肉记忆了,不出牌了
Java SDK的版本不断升级,这门语言始终在飞速发展,我们玩技术的也应该多多与时俱进,无论是新的语法糖还是内部实现(多表现为性能提升),都应多多关注,比如jdk7以后,支持try-with-resources语法糖,类只要实现了java.io.Closable接口,都可以受惠,这种语法糖直接在语言层面为你简化了 陈旧冗长的try-finally处理范式,来看:
正例:
FileInputStream in = null;try { in = new FileInputStream("in.txt");} catch (Exception e) { throw new RuntimeException("文件读写错误", e);} finally { if (in != null) { try { in.close(); } catch (Exception e) { // 主动吞咽异常 log略 } }}
反例:
try (FileInputStream in = new FileInputStream("in.txt")) {} catch (Exception e) { // log略 throw new RuntimeException("文件读写错误", e);}
8. 任何异常,只要能捕获住Exception兜底处理下,满满健壮感
在某些情况下,程序抛出的root异常并不一定都是Exception,要知道Java中还有一种异常的root叫做java.lang.Error,当潜在地,可能有Error抛出时,必须捕获Throwable。
正例:
static void weird1() throws NoSuchMethodError { throw new NoSuchMethodError("weird1");}static void weird2() throws Exception { weird2();}public static void main(String[] args) { try { weird1(); } catch (Exception e) { System.out.println("妥妥抓住weird1异常,妥妥滴!"); } try { weird2(); } catch (Exception e) { System.out.println("妥妥抓住weird2异常,奥利给!"); }}
反例:
try { weird1();} catch (Throwable e) { System.out.println("妥妥抓住weird1异常,妥妥滴!");}try { weird2();} catch (Throwable e) { System.out.println("妥妥抓住weird2异常,奥利给!");}
9. 自定义异常,吃饱了撑的吧
在适当的时候,或者说在业务变得越来越复杂的时候,自定义异常这件事就是顺理成章的了,来看:
正例:
static void bizA() throws RuntimeException { throw new RuntimeException("sth error");}static void bizB() throws RuntimeException { throw new RuntimeException("sth error");}static void call() { bizA(); bizB();}public static void main(String[] args) { try { call(); } catch (Exception e) { //现在就把这个异常show给你看,当看到sth error时,你一定不会关心是bizA还是bizB的错,easy life! System.out.println(e); }}
反例:
static class ExceptionOfA extends RuntimeException { public ExceptionOfA(String message) { super(message); }}static class ExceptionOfB extends RuntimeException { public ExceptionOfB(String message) { super(message); }}static void bizA() throws ExceptionOfA { throw new ExceptionOfA("sth error");}static void bizB() throws ExceptionOfB { throw new ExceptionOfB("sth error");}static void call() { bizA(); bizB();}public static void main(String[] args) { try { call(); } catch (ExceptionOfA e) { //针对bizA的异常,我们用方案xxx来应对 } catch (ExceptionOfB e) { //针对bizB的异常,我们用方案yyy来应对 }}
10. DRY原则,别整那洋玩意儿
DRY原则,Don't Repeat Yourself,拒绝重复代码!
正例:
static class ExceptionOfA extends RuntimeException {}static class ExceptionOfB extends RuntimeException {}static void bizA() throws ExceptionOfA {}static void bizB() throws ExceptionOfB {}static void call() { bizA(); bizB();}//我是异常处理方案xxxstatic void handleError(Exception e) {}public static void main(String[] args) { try { call(); } catch (ExceptionOfA e) { //针对bizA的异常,我们用方案xxx来应对 handleError(e); } catch (ExceptionOfB e) { //针对bizB的异常,我们也用方案xxx来应对 handleError(e); }}
反例:
public static void main(String[] args) { try { call(); } catch (ExceptionOfA | ExceptionOfB e) { //针对bizA和bizB的异常,我们都用方案xxx来应对 handleError(e); }}
11. 异常没写文档,遗漏处理,怪我咯?
异常文档化,这样让调用者或者维护者能够更清晰地知道你这个方法的约定、细节等等,这不仅仅体现在javadoc上,还体现在你的throws子句上(P.S. 这点不是强制的),现代的IDE还可以为有throws子句声明的异常自动生成try-catch处理的模版代码
正例:
static class AaException extends RuntimeException {}static class BbException extends RuntimeException {}static void biz(boolean a, boolean b) { if (a) throw new AaException(); if (b) throw new BbException();}public static void main(String[] args) { //程序员小明引入了某个jar包,biz方法是该jar包中某类的某方法 //小明扫了眼javadoc,没有任何关于参数和异常的描述,调用无忧! biz(false, true);}
反例:
/** * @throws AaException 这是一个xxx异常,发生它时,通常意味着你应该... * @throws BbException 这是一个yyy异常,发生它时,通常意味着你应该... * */static void biz(boolean a, boolean b) throws AaException, BbException { if (a) throw new AaException(); if (b) throw new BbException();}public static void main(String[] args) { // 程序员小明引入了某个jar包,biz方法是该jar包中某类的某方法 // 小明扫了眼javadoc,发现有关于异常的详细描述,他赶紧使用IDE的自动生成try/catch处理块功能,编写更为健壮的程序 try { biz(false, true); } catch (AaException e) { //处理略... } catch (BbException e) { //处理略... }}
12. 根因异常,这又是什么鬼
抛出异常的同时,应带上底层的异常堆栈,不要埋没根因(root cause,即追本溯源,根本原因异常)
正例
static class ServiceException extends RuntimeException { public ServiceException(String message) { super(message); }}static class DbException extends RuntimeException { public DbException(String message) { super(message); }}static void service() throws ServiceException { try { dbOps(); } catch (Exception e) { throw new ServiceException("业务异常"); }}static void dbOps() throws DbException { throw new DbException("删库异常");}public static void main(String[] args) { try { service(); } catch (Exception e) { //业务异常而已,小场面! e.printStackTrace(); }}
反例
static class ServiceException extends RuntimeException { public ServiceException(String message, Throwable cause) { super(message, cause); }}static class DbException extends RuntimeException { public DbException(String message) { super(message); } public DbException(String message, Throwable cause) { super(message, cause); }}static void service() throws ServiceException { try { dbOps(); } catch (Exception e) { //抛出异常的同时,带上底层的异常堆栈,不要埋没根因(root cause,根本原因异常) throw new ServiceException("业务异常", e); }}static void dbOps() throws DbException { throw new DbException("删库异常");}public static void main(String[] args) { try { service(); } catch (Exception e) { //业务异常而已,小场面!等一等、删库了!!! e.printStackTrace(); }}
13. 喂,你这接口返回了个啥,你品你细品(TBD)
14. 大俗大雅,说说null
编程经验越来越丰富的同学们,开始喜欢在方法有返回值时,给个空的list、对象什么的了,而不是直接返回null,这样可以防止调用者调用你这个方法时,收到空指针异常,其实这倒不是强制的。本来调用者对null的处理就是他的责任所在,况且空对象、空list都是对象,有内存开销。还有就是,处理空list、空对象还有一些高级技巧,并不简单的。所以,脑细胞留给其它问题吧,骚年!
15. 和NPE的世界大战(TBD)
16. Spring事务,大佬罩着,异常无忧
Spring的事务中,如果抛出了异常,那么事务是可以自动回滚的,但那有个前提,抛出的异常得是RuntimeException(或其子类)
正例
@Transactionalvoid service() { //1.往db里insert了条记录 //2.往db里update了条记录 //3.模拟抛出异常,外面有Spring的@Transactional老大哥罩着,之前对db的操作定能回滚,easy life! throw new Exception("调用失败");}
反例
//此处必须显式声明,对Exception也需要按需回滚//(老大哥:我只能自动识别到RuntimeException,而对于Exception,你不说我怎么知道你想要?)@Transactional(rollbackFor=Exception.class)void service() { //1.往db里insert了条记录 //2.往db里update了条记录 //3.模拟抛出异常,外面有Spring的@Transactional老大哥罩着,之前对db的操作定能回滚,easy life! throw new Exception("调用失败");}
本文部分参考了阿里巴巴Java开发手册,融入了自己的一些解读和扩展,愿对你有所帮助
___________________________我是一条分割线之:第一重已成___________________________
神功未成,继续修炼...
历史文章:
管理类
软件行业从起步到转型,你所要了解的妙点
项目经理的升迁之道
技术类
Java异常处理葵花宝典(第一重)