Java 异常入门(1/2)

本文尝试以尽量详细的方式介绍 Java 当中的异常概念和处理机制。本文适合 Java 初学者阅读。java

什么是异常

异常是发生在程序运行过程当中的,阻断正常流程中的指令执行的事件。segmentfault

当一个方法在执行当中发生错误时,这个方法就会建立一个特别的对象,将其交付给 Java 运行环境处理。这个对象被称做“异常对象”(或简称异常),它当中包含了异常事件的类型和状态等等信息。 建立这个对象并将其交付给 Java 运行环境的过程,称做“抛出异常”。指针

异常对象:
全部的异常对象都继承于 java.lang.Exception 类。不一样类型的异常被定义成它的不一样子类。常见的异常类型有 java.io.IOException(读写异常),java.lang.NullPointerException(空指针异常)等。code

当一个方法抛出异常(或“异常对象”)时,运行环境就会尝试寻找某个方法去处理它。全部可以处理该异常的方法,都来自一个叫作“调用堆栈”的方法列表。这个列表的最顶层就是抛出该异常的方法,往下是调用该方法的方法,而后依次类推。对象

例如a()方法当中调用了b()方法,b()方法当中调用了c()方法,那么当c()方法抛出异常时,它就在调用堆栈的最顶层,往下是b()方法,再往下就是a()方法。blog

运行环境会沿着调用堆栈往下寻找,寻找方法当中是否存在可以处理这个异常的代码块。这样的代码块叫作“捕获异常”。运行环境将异常对象交给这个“捕获异常”的代码块处理。若是运行环境在调用堆栈中自始至终未能找到捕获这个异常的代码块,那么整个程序将终止运行。继承

处理异常的方式:捕获或声明

当你编写一个方法 a(),当中调用了方法 b(),而该方法可能抛出异常,那么你有两种选择:事件

1、在 a() 方法中用 try-catch 结构捕获 b() 方法抛出的异常,方式以下:get

// 示例1
try {
    ...
    b();
    ...
} catch (Exception e) {
    // 处理异常对象
}

2、在 a() 方法上声明为抛出 b() 方法所抛出的异常,像这样:编译器

// 示例2
public void a() throws Exception {
    ...
    b();
    ...
}

这样的话,调用 a() 方法的那个方法,一样面临两种选择:捕获,或继续抛出。若是你不在这两种处理方式当中选择一种,那么编译器就会断定你的代码存在错误,并拒绝编译。

开始的时候说过,异常事件会阻断指令的执行。也就是说,执行到 b() 方法的时候,若是它抛出了异常,那么它后面的指令都不会执行了。好比在第一种状况下(示例 1)抛出异常,那么从 b();} catch (Exception e) { 之间的全部语句都不会执行;在第二种状况下(示例 2)则是从 b(); 到 a() 方法结束之间的全部语句都不会执行。

接下来咱们分别介绍这两种处理方式。

捕获异常的方式:try-catch-finally

在 Java 中,捕获异常的方式是编写 try-catch-finally 代码块。下面是一个例子,读取指定文件并将文件内容输出到控制台:

// 示例3
public static void main(String[] args) {
    java.io.File file = new File("C:\\1.txt");
    java.io.BufferedReader reader = null;
    String line;

    try {
        reader = new BufferedReader(
                new java.io.FileReader(file));          // 1

        while ((line = reader.readLine()) != null) {    // 2
            System.out.println(line);
        }

    } catch (IOException e) {                           // 3
        e.printStackTrace();

    } finally {                                         // 4
        if (reader != null) {
            try {
                reader.close();                         // 5
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

上面的例子中,1 处的 new FileReader(file) 和 [2] 处的 readLine() 方法均可能抛出 java.io.IOException,因此咱们用 try {...} 将它们包起来,表示这部分代码执行的时候可能抛出异常。

[3] 处为 catch 块,这部分决定了当异常真的发生时,该如何处理。假如 1 处抛出了异常,那么 Java 运行环境将跳过 try 块后面的 while 部分,直接转到 catch 块来处理这个异常。

catch 后面的 (IOException e) 声明了被捕获的异常对象,你能够在 catch 块中使用它。在这里咱们简单的调用 e.printStackTrace() 方法,将异常信息输出到控制台。

注意,try 块中抛出的异常类型必须与 catch 块声明所要捕获的异常类型匹配,不然编译器将拒绝编译。关于这一点的详情将在后面说明。

[4] 处为 finally 块,这部分决定了当 try 块和/或 catch 块执行完毕后,还要执行哪些代码,这部分能够看做是“老是会执行的”、“善后工做”。在这里咱们尝试关闭 reader 对象,不管是否出现异常。

注意 reader.close(); 方法也可能抛出异常,因此 [5] 这里又须要用 try-catch-finally 块包起来。你固然已经注意到,这里没有 finally 部分。其实 try-catch-finally 块有三种写法,分别是:

1. try {...A...} catch(Exception e) {...B...} finally {...C...}
2. try {...A...} catch(Exception e) {...B...}
3. try {...A...} finally {...C...}

写法 1 的意思是:尝试 A;若是出现异常则执行 B;不论是否出现异常,都执行 C。
写法 2 的意思是:尝试 A;若是出现异常则执行 B。
写法 3 的意思是:尝试 A;若是出现异常则不理会抛出去;不论是否出现异常,都执行 C。

在写法 3 中,若是 try 部分抛出了异常,则须要在当前方法中声明。声明抛出异常的方式,将在接下来讲明。

声明抛出异常

任何一个方法均可以经过 throws 关键字声明本身可能抛出异常。下面是一个例子:

// 示例4
public void f() throws Exception {
    ...
}

在这个例子中,方法 f() 声明本身可能会抛出 java.lang.Exception 类型的异常。除了 Exception 外,方法也能够明确的指出本身可能抛出哪一种类型的异常,例如:

// 示例5
public void f() throws java.io.IOException {
    ...
}

若是方法当中的某条语句可能会抛出 A 类型的异常,那么方法就不能声明为抛出 B 类型的异常。下面的作法是错误的:

// 示例6
public void g() throws java.lang.NullPointerException {
    f();
}

由于 f() 已经声明为抛出 IOException,而 g() 并无将该异常捕获,所以它也必须声明为 “throws IOException”。若是编译器发现这种不匹配的状况,就会拒绝编译。

有一种状况例外,就是 B 类型的异常是 A 类型的异常的父类。这意味着 B 类型的异常在业务逻辑上涵盖了 A 类型,因此咱们相信凡是能处理 B 的天然也能处理 A。

所以,既然 java.lang.Exception 是全部异常的父类,那么凡是声明抛出该类型异常的方法,天然能够抛出任意类型的异常。也就是说,当一个方法声明为 “throws Exception” 时,它能够抛出任何类型的异常。注意,由于这种声明方式会给方法的调用者带来困扰,因此咱们尽可能不要这么作。

回顾一下示例 3:
如今回顾一下示例 3 中 catch(IOException e) {...} 部分。若是将它改成 catch(Exception e) {...},就表示捕获全部类型的异常,即它前面的 try 部分不论抛出什么类型的异常,它均可以处理。某些状况下这样作是合理的,由于咱们不但愿当遇到某个特定种类的异常时,程序终止运行。


接下来请看

Java 异常入门(2/2)

相关文章
相关标签/搜索