异常处理机制详解
Posted potato-not-tomato
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了异常处理机制详解相关的知识,希望对你有一定的参考价值。
# 前言
本文主要是对Java异常处理机制的阐述,了解Java的异常机制的设计和分类,及Java异常有哪些坑,如何在自定义异常类时避免采坑。
# 异常机制分类
异常情况是指阻止当前方法或作用域继续继续执行的情况。在Java中异常也是对象,我们可以像创建其他对象一样,用new在堆上创建异常对象。
从上图可以看到Throwable是所有异常类型的根类,它有两个重要的子类:Exception和Error。
- Error(错误)
Error表示编译时和系统错误(除特殊情况外我们无需关注),比如代码允许是JVM运行错误,或内存不足时OutOfMemoryError。
- Exception(异常)
Exception是可以抛出/处理的异常。在Java类库、用户方法及运行时故障都可能抛出Exception类型异常,我们程序员需要关注的主要是Exception。它又分为运行时异常和非运行时异常。
运行时异常:由RuntimeException和其子类异常组成。比如NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)。这些异常通常是非受检异常,可以捕获处理或者不处理。一般有程序逻辑引起的。运行时异常的特点是Java编译器编译时不会检查它,就算有这种异常编译也能通过,究其原因,RuntimeException代表的是编程错误。
非运行时异常:包括RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常。
# try-catch-finally捕获异常
在Java中使用try-catch或者try-catch-finally捕获异常。
## try块
对于有可能出现异常情况的代码块Code,我们可以把它放在try块里。
try { //可能发生异常的代码块 }
## 异常处理程序
异常处理程序必须紧跟在try块后,以关键字catch表示。当异常被抛出时,异常处理机制将负责搜寻参数与异常类型相匹配的第一个程序,然后进入catch子程序执行。
try { //可能发生异常的代码块 } catch (Type1 id1) { //捕获并处理异常类型为Type1的异常 } catch (Type2 id2) { //捕获并处理异常类型为Type1的异常 } finally { //无论如何都会走到的代码 //有如下极端情况不会走到finally代码块,但一般不考虑 //比如CPU掉电、线程异常终止等 } // etc...
有时也可以采用maltiple catch,具体参考以下代码段。
## throw和throws
我们在编程时,需要针对某种异常情况抛出异常给客户端,代码如下
if (s == null) { throw new NullPointerException(); }
throws是一种“异常说明”方式,它属于方法声明的一部分,跟在形式参数列表之后。
void func() throws Exception1, Exception2 {}
这种异常说明的方式,可以强制函数使用者强制处理该异常情况。在定义抽象基类和接口时这种能力很重要,这样派生类就可以处理这些预先声明的异常。
从上面可以看出throw主要是用来中断程序执行并移交异常对象到运行时处理。
throws用于声明方法可抛出的异常,是异常说明的一种机制。
## 使用finally做清理工作
对于一些代码,希望无论try块是否有异常抛出,都能得到执行,比如打开的文件句柄或者网络连接,可以使用try-catch-finally,代码如下所示。
public class FinallyWorks { static int count = 0; public static void main(String[] args) { while (true) { try { // count为0时抛异常 if (count ++ == 0) { throw new IOException(); } System.out.println("No exception"); } catch (Exception e) {//该句可以捕获所有异常 System.out.println("IOException"); } finally { System.out.println("In finally clause"); if (count == 2) break; } } } }
/** * Output **/ IOException In finally clause No exception In finally clause
## 新特性
### multiple exception
如果一个try块中有多个异常要被捕获,catch块中的代码会变丑陋的同时还要用多余的代码来记录异常。有鉴于此,Java 7的一个新特征是:一个catch子句中可以捕获多个异常。示例代码如下:
catch(IOException | SQLException | Exception ex){ log.warn(ex); throw new MyException(ex.getMessage()); }
### try-with-resources
try-with-resources[1][2] 语句会确保在try语句结束时关闭所有资源。实现了java.lang.AutoCloseable或java.io.Closeable的对象都可以做为资源。使用try-with-resources进行资源的自动关闭,在try子句中能创建一个资源对象,当程序的执行完try-catch之后,运行环境自动关闭资源。示例代码如下:
/** * code 1 **/ try (FileInputStream fis = new FileInputStream("example.java")) { // line 1 System.out.println("fis created in try-with-resources"); doSomething(); // line2 } catch (Exception e) { e.printStackTrace(); }
在Java7之前我们使用finally进行资源的关闭,如下所示
/** * code 2 **/ FileInputStream fis = new FileInputStream("example.java"); try { System.out.println("fis created in try-with-resources"); doSomething(); // line 3 } catch (Exception e) { e.printStackTrace(); } finally { if (fis != null) { fis.close();// line 4 } }
异常屏蔽
请参见参考文献[1][2]
# 正确的使用异常
## 不要在finally中使用return关键字。
finally块中return返回后方法结束执行,会覆盖try块中的return语句,换句话说就是屏蔽了try块中的return语句。
/** * @author liangk * @date 18/09/2018 */ public class FinallyReturn { public static void main(String[] args) { String result = finallyReturnTest(); System.out.println(result); } public static String finallyReturnTest() { try { System.out.println("finallyReturnTest start"); String result = "Hello EveryBody!"; return result; } finally { return "The finally block will be printed in the end"; } } }
/** * Output */ finallyReturnTest start The finally block will be printed in the end
## finally 块必须对资源对象、流对象进行关闭。
如果JKD7及以上版本,可以使用上文介绍的try-with-resources方式
## 避免直接抛出RuntimeException及其子类。
更不允许抛出Exception或Throwable(建议抛出具体的异常对象)
## 建议采用预检查方式规避RuntimeException异常,而不应该catch的方式处理。
public void readPreferences(String fileName) { InputStream in = new FileInputStream(fileName); }
上面的程序如果fileName是null,就会抛出NullPointerException,由于没有第一时间暴露问题,堆栈信息费解,需要相对复杂的定位。如果我们采取下面的方式,就很容易解决问题
public void readPreferences(String fileName) { Objects.requireNonNull(fileName); InputStream in = new FileInputStream(fileName); }
## 不允许直接吞没异常。
直接吞没异常,既不处理也不抛出,可能会导致难以诊断的异常情况,无法判断异常从哪里结束,什么原因产生的异常情况。
## try块只包含可能会出现异常的必要代码段。
- try-catch 代码段会产生额外的性能开销,或者换个角度说,它往往会影响JVM对代码进行优化,所以建议仅捕获必要的代码段,不能包住整段代码。
- Java每实例化一个Exception,都会对当时的栈进行快照,这是一个相对较重的操作。如果异常频繁发生,开销就无法忽略。
## 不允许使用异常实现流程控制和条件控制。
我们可以利用breakcontinue if else 配合finally实现流程控制。但利用异常控制流程,比通常意义上的条件语句(if/else、switch)要低效。
## try块放到事务代码中,catch异常后,如果需要回滚事务,一定注意手动回滚事务。
# 参考文献
[1] [详解try-with-resource](http://www.oracle.com/technetwork/cn/articles/java/trywithresources-401775-zhs.html)
[2] [try-with-resource官方文档](https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html)
以上是关于异常处理机制详解的主要内容,如果未能解决你的问题,请参考以下文章