新版Java异常
1.异常处理机制和体系结构
Java异常是指在程序运行过程中出现错误,从而影响程序流程的正常运行。而异常处理机制可以保证程序出现错误后,控制接下来的程序流程,是选择定位错误信息,还是抛出异常或捕获异常、还是避免程序非正常退出,都取决于我们。
- NullPointerException(空指针异常)
- Class CastException(类型强制转换异常)
- ClassNotFoundException(指定类不存在)
- ArrayIndexOutOfBoundsException(数组下标越界)
- ArithmeticException(数***算异常)
- SQLException(数据库异常)
- InstantiationException(实例化异常)
- NumberFormatException(数字格式异常)
通过上面的异常体系结构图可以清晰看出Java把所有异常都当做对象来处理,所有异常的基类是Throwable类(可抛出),两大子类分别是Error和Exception,Error异常表示程序出现了十分严重、不可恢复的错误,例如Java虚拟机出现错误,这种情况仅凭程序自身是无法处理的,在程序中也不会对Error异常进行捕捉和抛出。Exception又分为RuntimeException(未检查异常)和CheckedException(检查异常),两者区别如下:
主要区别在于其处理方式。检查型异常需要使用try, catch和finally关键字在编译期进行处理,否则会出现编译器会报错。对于非检查型异常则不需要这样做。
<mark>RuntimeException(运行时异常):</mark>
RuntimeException及其子类都统称为非受检查异常,例如:NullPointExecrption(空指针异常)、NumberFormatException(字符串转换为数字)、ArrayIndexOutOfBoundsException(数组越界)、ClassCastException(类型转换错误)、ArithmeticException(算术错误)等。只有在运行过程中出现错误,才会被检查的异常,是unchecked Exception,编译器不会对运行时异常进行检查和处理。但如果出现了运行时异常,则表明编程出现了错误,则应该找到错误并修改,而不是对其捕获。
<mark>CheckedException(检查异常,即非运行时异常):</mark>
相信大家在写IO操作的代码的时候,一定有过这样的记忆,对File或者Stream进行操作的时候一定需要使用try-catch包起来,否则编译会失败,这是因为这些异常类型是受检查的异常类型。所有非RuntimeException且来自于Exception的异常都是检查异常,该异常会在程序运行之前被编译器检查,对于检查异常,必须对其进行处理,可以选择捕获或抛出。但应该遵循该原则:谁知情谁处理,谁负责谁处理,谁导致谁处理。
1 因为在你请求了不存在的系统资源的时候,一段强壮的程序必须能够优雅的处理这种情况。
2可以使用catch或finally来确保数量受限的系统资源(比如文件描述符)在你使用后尽早得到释放。
2.异常处理方式
抛出或捕获异常,离不开这5个关键字:try、catch、finally、throw、throws,关于它们的用法和注意点,会在下面一一介绍,并附带实例。
- try、catch和finally都是不能单独使用的,只能像try-catch 或者 try-finally,或者三者一起使用try-catch-finally。
- try语句块监控代码,出现异常就停止执行下面的代码,然后将异常移交给catch语句块来处理。
- finally语句块中的代码一定会被执行,常用于回收资源 。
- throws:往上级调用者抛出一个异常。
- throw :抛出一个异常,至于该异常被捕获还是继续抛出都与它无关。
关于return语句在try-catch-finally语句块的执行顺序结论
- 1.首先,finally语句块一定会执行,若finally存在return语句,则会优先于try和catch中的return语句执行,当执行了return,则try和catch中的return语句不再执行。
- 2.如果finally语句中没有return,按照正常流程执行,try或catch在执行return语句之前,会先执行finally中的代码,然后再反回去执行return。
- 3.如果没有finally语句,则try或catch就按照正常程序流程执行。
- 4.反过来想,当finally语句存在时,若try或catch执行了return,那finally语句就得不到执行,那finally语句就毫无作用,两者引起悖论。
finally遇见如下情况不会被执行
- 在前面的代码中用了System.exit()退出程序。
- finally语句块中发生了异常。
- 程序所在的线程死亡。
- 关闭CPU。
3.声明异常和捕获异常应遵循的原则:
1.声明异常的第一位不要声明Exception异常,因为Exception是所有检查异常的父类,所以会忽略剩下的异常。
2.捕捉异常时第一位不要捕获Exception,应该放在最后面,保证前面的异常能被捕获到。
3.即父类异常要放在子类异常之后,避免异常匹配时被屏蔽。
4.在类继承的时候,方法覆盖时如何进行异常抛出声明
1)父类的方法没有声明异常,子类在重写该方法的时候不能声明异常;
2)如果父类的方法声明一个异常exception1,则子类在重写该方法的时候声明的异常不能是exception1的父类;
3)如果父类的方法声明的异常类型只有非运行时异常(运行时异常),则子类在重写该方法的时候声明的异常也只能有非运行时异常(运行时异常),不能含有运行时异常(非运行时异常)。
5.Java空指针异常
NullPointerException
NullPointerException由RuntimeException派生出来,是一个运行级别的异常。意思是说可能会在运行的时候才会被抛出,而且需要看这样的运行级别异常是否会导致你的业务逻辑中断。
1 当应用程序试图在需要对象的地方使用 null 时,抛出该异常。这种情况包括:
- 调用 null 对象的实例方法。
- 访问或修改 null 对象的字段。
- 将 null 作为一个数组,获得其长度。
- 将 null 作为一个数组,访问或修改其时间片。
- 将 null 作为 Throwable 值抛出。
出现在任何一个位置上的对象引用都有可能为 null,在进行访问,赋值,取值,类型转换等操作时,首先判断该对象是否为 null,否则极易抛出空指针异常;
2 比如变量为空,而你没有去判断,就直接使用,就会出现NullPointException。写程序时严谨些,尽量避免了,例如在拿该变量与一个值比较时,要么先做好该异常的处理如: if (str == null) { System.out.println(“字符为空!”); } 当然也可以将这个值写在前面进行比较的,例如,判断一个String的实例s是否等于“a”,不要写成s.equals(“a”),这样写容易抛出NullPointerException,而写成"a".equals(s)就可以避免这个问题。不过对变量先进行判空后再进行操作比较好
6.如何避免空指针?
1、字符串比较,常量放前面
if(status.equals(SUCCESS)){
}
这个时候 status 可能为 null 造成空指针异常,应该把常量放前面,就能避免空指针异常。
if(SUCCESS.equals(status)){
}
2、初始化默认值
在对象初始化的时候给它一个默认值或者默认构造实现,如:
User user = new User();
String name = StringUtils.EMPTY;
3、返回空集合
在返回一个集合的话,默认会是 null,统一规范返回一个空集合。
举个 List 例子,如:
public List getUserList(){
List list = userMapper.gerUserList();
return list == null ? new ArrayList() : list;
}
这样接收方就不用担心空指针异常了,也不会影响业务。
4、断言
断言是用来检查程序的安全性的,在使用之前进行检查条件,如果不符合条件就报异常,符合就继续。
Java 中自带的断言关键字:assert,如:
assert name == null : "名称不能为空";
输出:
Exception in thread "main" java.lang.AssertionError: 名称不正确
不过默认是不启动断言检查的,需要要带上 JVM 参数:-enableassertions 才能生效。
Java 中这个用的很少,建议使用 Spring 中的,更强大,更方便好用。
Spring中的用法:
Assert.notNull(name,"名称不能为空");
5、Optional
Optional 是 JDK 8 新增的新特性,再也不用 != null 来判断了,这个在一个对象里面的多个子对象连续判断的时候非常有用。
这里不再详细介绍了,具体看这篇文章:JDK8新特性之Optional