异常处理机制详解

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)








以上是关于异常处理机制详解的主要内容,如果未能解决你的问题,请参考以下文章

java异常处理详解!!

Java异常处理机制详解(建议收藏保存)。

Spring MVC学习—项目统一异常处理机制详解与使用案例

Java中的异常和处理详解

详解Java中的异常和处理

异常处理机制详解