认识异常
异常基础
所谓的异常,就是程序按照正常执行逻辑走着走着遇到问题,崴了脚或闪了腰,已经无法再继续走下去了。怎么办呢?就像有病要治病一样,有异常就得处理异常。Java提供了基本的语法来处理异常:一中是throw(s)语法,叫抛异常;一种是try-catch-finally语法,叫做捕获异常。Java是面向对象的,在Java的世界里一切皆是对象,所以异常自然也被看成是一种对象,当异常发生,就是创建了一个异常对象,我们使用两种语法来处理异常对象就行了,语法也很简单,下面只简单示例:
public class Test { //继续外抛异常 public void test1() throws FileNotFoundException { new FileInputStream("D://test.txt"); } //自己捕获异常 public void test2() { try { new FileInputStream("D://test.txt"); } catch (FileNotFoundException e) { e.printStackTrace(); }finally{ //不管异常发没发生都总会执行的部分,通常用来处理收尾工作,入内存清理,关闭流等等 } } //自己主动创建异常对象并抛出 public void test3() throws Exception { throw new Exception("抛出异常"); } }
这里很基础,还是说三点:
① 最好从英文词性上区分throw和throws两个关键字。前者是动词,代表抛出,很明显是用在方法内部,执行异常对象的抛出动作;后者是名词,用在方法后面,用来声明方法执行可能抛出的一个或多个异常,多个异常之间用逗号隔开;
② finally块不是必须的,除非业务中有必须要执行的部分;如果有finally块,那该部分语句无论方法是否发生异常甚至中途执行return终止方法,该部分都会得到执行;finally块中不能使用return,会造成异常丢失;
③ 如果你对异常是该抛出和自己处理犹豫不定,那布衣博主告诉你一个原则:该背锅背锅,不能背就甩锅!是的,异常和日常中的甩锅行为类似。当别人向你甩来的锅如果你能背,那就接住大胆背锅处理异常,不然就甩锅吧给上层调用者处理。
异常体系
打开JDK API 文档,找到 lang包中 Throwable类,这就是Java异常的祖宗类了。你会发现异常家族体系相当庞大,但大而不乱,因为这祖宗也就两个直接子类 Error 和 Exception。Error 通常指系统错误,程序员无需关心;Exception才是需要抛出或者捕获的异常的基类,其下有很多子类孙类,支系繁茂。对于Exception类型异常,又分了编译期异常和运行时异常两大类,前者是在编码阶段必须处理(捕获或者抛出)的异常类型,不处理程序无法正常编译;后者不用主动处理,通常是程序逻辑上的异常,在程序运行时由虚拟机抛出。只需记住如下谱系图即可:
自定义异常
简单封装
Java标准类库中的异常虽然已经很多了,但是那是标准库中通用的异常情况,不同项目会有不同的业务逻辑,这就需要我们根据自己的业务逻辑来定义、处理自己的异常。最好的自定义方式是从跟自己业务处理意思最接近的异常类继承,比如定义运行时异常可以继承RuntimeException,定义IO异常可以继承IOException。不过对异常情形限定得太窄其实也没有必要了,直接继承异常基类Exception即可:
public class MyException extends Exception { public MyException() { super(); } public MyException(String message) { super(message); } public MyException(String message, Throwable cause) { super(message, cause); } }
这样简单的继承,只是屏蔽了底层的具体异常类,对外暴露的是和自己业务相关的语义化的异常类名而已,功能依旧是父类异常的功能。这样的自定义异常显然功能太父类化了。更多的情况下,我们希望异常类能有比父类更完备的功能,以帮助程序向客户端调者反馈更友好的信息。这个时候要跳出异常的字面意思去理解Java的异常机制,不能简单的认为异常就是发生了不想看到的错误,而是把异常看成一种控制流,是程序流程处理的一部分,控制着我的程序在情况1 时走哪一步,在情况2下又该执行哪一步。比如通过JNA调用底层动态库获取硬件信息,由于程序运行的不可预见性,Java程序员和底层语言开发者之间会根据返回值约定一些异常情形,比如 0 代表什么,1 代表什么,诸如此类:
public void func(int result) throws MyException1 ,MyException2,MyException3{ switch (result){ case 1:throw new MyException1(); case 2:throw new MyException2(); case 3:throw new MyException3(); ... } }
像上面这种,利用穷举的方式写代码肯定是很不明智的,更多的时候,我们会采用枚举与自定义异常相结合的方式,来处理异常的流程控制,以更好的发挥自定义异常的自定义作用。
异常与枚举
先定义约定的异常情形枚举:
//异常状态枚举 public enum ErrorCode { SUCCESS(0, "成功"), NOTFOUND(1, "未找到验证设备"), READFAIL(2, "读取验证设备失败"), OVERDUE(3, "验证设备已过期"), UNKNOWN(4, "未知原因的失败"); int code; String msg; ErrorCode(int code, String msg) { this.code = code; this.msg = msg; } public int getCode() { return code; } public String getMsg() { return msg; } //根据状态码获取状态信息 public static ErrorCode getErrorCode(int code) { for (ErrorCode errorCode : values()) { if (errorCode.code == code) { return errorCode; } } return null; } @Override public String toString() { return "ErrorCode{" + "code=" + code + ", msg='" + msg + '\'' + '}'; } }
再定义自己的异常类:
public class LibException extends Exception { private int code; private ErrorCode errorCode; public ErrorCode getErrorCode() { return errorCode; } LibException(int code) { this.code = code; this.errorCode = ErrorCode.getErrorCode(code); } public int getCode() { return code; } public LibException(String message) { super(message); } public LibException(String message, Throwable cause) { super(message, cause); } }
通过枚举和自定义异常的结合,便能在程序中动态的根据错误码反馈给上层调用者友好的异常信息。测试:
public class Test { public static void chenbenbuyi(int result) throws LibException { if(result>=0&&result<=4)throw new LibException(result); System.out.println("result: "+result); } public static void main(String[] args) { try { chenbenbuyi(3); } catch (LibException e) { System.err.println("错误码:"+e.getCode()+" 错误信息:"+e.getErrorCode().getMsg()); } } }
友好输出反馈: