java葵花宝典_Java异常处理葵花宝典(第一重)

软件编程语言琳琅满目,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

编译器报错高清无码图

发红了,赶紧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异常处理葵花宝典(第一重)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值