面试官:小伙子,你给我说一下Java Exception 和 Error 的区别吧?

欢迎你们关注个人公众号:前程有光,金三银四跳槽面试季,整理了1000多道将近500多页pdf文档的Java面试题资料,文章都会在里面更新,整理的资料也会放在里面### 前言
昨天在整理粉丝给我私信的时候,发现了一个挺有意思的事情。是这样的,有一个粉丝朋友私信问我Java 的 Exception 和 Error 有什么区别呢?说他在面试的时候被问到这个问题卡壳了,最后还好也是有惊无险的过了。在恭喜这位粉丝的同时,咱们再回过头来这个问题,其实在面试中这是个常见的连环问题了,大多数面试官都喜欢用这个话题发问。当时看完当时内心也就下了个决心,必定要写篇文章把 Java 的异常相关讲明白,让你们看完以后再遇到相似问题就会有所准备!java

throw 语句

有点 java 基础的同窗应该都知道 throw 这个语句吧。咱们都知道throw 语句起到的做用,它会抛出一个 throwable 的子类对象,虚拟机会对这个对象进行一系列的操做,要么能够处理这个异常(被 catch),或者不能处理,最终会致使语句所在的线程中止。程序员

那么 JVM 究竟是怎么作的呢?让咱们一块儿试试看吧:面试

首先写一段代码,throw 一个 RuntimeException:数组

package com.company;

public class TestException {
    public static void main(String[] args) {
        throw new RuntimeException();
    }
}

编译后,到 class 文件所在目录,用javap -verbose 打开 .class 文件:ide

javap -verbose TestException

能够看到一些字节码。咱们找到 TestException.main 函数对应的字节码:函数

public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: new           #2                  // class java/lang/RuntimeException
         3: dup
         4: invokespecial #3                  // Method java/lang/RuntimeException."<init>":()V
         7: athrow
      LineNumberTable:
        line 5: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       8     0  args   [Ljava/lang/String;

code 部分,实际上前三行就是对应 new RuntimeExcpetion(),因为主题缘由,这里就不展开了。重头戏是后面这个 athrow,它到底作了什么呢?工具

1\. 先检查栈顶元素,必须是一个java.lang.Throwable的子类对象的引用;

2\. 上述引用出栈,搜索本方法的异常表,是否存在处理此异常的 handler;

    2.1 若是找到对应的handler,则用这个handler处理异常;

    2.2 若是找不到对应的handler,当前方法栈帧出栈(退出当前方法),到调用该方法的方法中搜索异常表(重复2);

3\. 若是一直找不到 handler,当前线程终止退出,并输出异常信息和堆栈信息(其实也就是不断寻找 handler 的过程)。

能够看到 throw 这个动做会形成几个可能的反作用:ui

  1. 终止当前方法调用,并传递异常信息给方法调用方;
  2. 若是异常一直没法在方法的异常表里找到 handler,最终会致使线程退出。

好了,到这里咱们已经搞明白 throw 到底干了些啥。可是咱们注意到, athrow 指令寻找的是一个 java.lang.Throwable 的子类对象的引用,也就是说 throw 语句后面只能跟 java.lang.Throwable 的子类对象,不然会编译失败。那么Throwable 究竟是个什么东西呢?this

Throwable 类簇

Throwable 顾名思义,就是能够被 throw 的对象啦!它是 java 中全部异常的父类:.net

public class Throwable implements Serializable {
    ...
}

JDK 自带的异常类簇,继承关系大概是这个样子的:

面试官:小伙子,你给我说一下Java Exception 和 Error 的区别吧?

首先能够看到 Throwable 分红两个大类,Exception 和 Error

Error

Error 是 java 虚拟机抛出的错误,程序运行中出现的较严重的问题。

例如,虚拟机的堆内存不够用了,就会抛出 OutOfMemoryError。这些异经常使用户的代码无需捕获,由于捕获了也没用。

这就比如船坏了,而船上的乘客即使知道船坏了也没办法,由于这不是他们能解决的问题。

Exception

Exception 是应用程序中可能的可预测、可恢复问题。

啥意思呢?也就是说Exception都是用户代码层面抛出的异常。换句话说,这些异常都是船上的乘客本身能够解决的。例如常见的空指针异常NullPointerException,取数组下标越界时会抛出ArrayIndexOutOfBoundException

这些都是“乘客”的错误操做引起的问题,因此“乘客”是能够解决的。

到了这里,粉丝问到的那道面试题,是否是就已经解决了呢?

CheckedException 和 UncheckedException

我前面也说了,这是个常见的连环问题。那么解决了第一个问题,面试官接下来会问什么呢?

经过上面一节的叙述你们能够看到,就 Throwable 体系自己,与程序员关系比较大的其实仍是 Exception 及其子类。由于船上的乘客都是程序员们创造,因此他们的错误行为,程序员仍是要掌握得比较透彻的。

Exception 可分为两种,CheckedExceptionUncheckedException

  • UncheckedException

顾名思义,UncheckedException 也就是能够不被检查的异常。JVM 规定继承自 RuntimeException 的异常都是 UncheckedException.

  • CheckedException

全部非 RuntimeException 的 Exception.

那么问题来了,什么叫 被检查的异常 ? 谁检查?

throws 语句

试想一下如下这个开发场景:

  • 同窗 A 写了一个工具类,编译后打成了一个 jar 包给同窗 B 使用
  • 同窗 B 调用这个工具类的时候,因为应用场景不一样,被抛出了不少不一样类型的异常,每出现一种新的异常,同窗 B 都要修改代码去适配,很是痛苦。。。

为了解决这个场景中出现的问题,JVM 规定,每一个函数必须对本身要抛出的异常心中有数,在函数声明时经过 throws 语句将该函数可能会抛出的异常声明出来:

public Remote lookup(String name)
        throws RemoteException, NotBoundException, AccessException;

这个声明就是前面说的被检查的异常

那么能够不被检查的异常又是咋回事呢?

其实 CheckedExcpetion 之因此要被 Check,主要仍是由于调用方是有呢你处理这些异常的。

java.net.URL 这个类的构造函数为例:

public final class URL implements java.io.Serializable {
    ...
    public URL(String protocol, String host, int port, String file,
               URLStreamHandler handler) throws MalformedURLException {
       ...
       if (port < -1) {
                throw new MalformedURLException("Invalid port number :" +
                                                    port);
            }
    }
}

MalformedURLException就是一种checked exception. 当输入的 port &lt; -1 时,程序就会抛出 MalformedURLException 异常,这样调用方就能够修正port输入,获得正确的URL了。

可是有一些状况,好比下面这个函数:

public void method(){
   int [] numbers = { 1, 2, 3 };
   int sum = numbers[0] + numbers[3];
}

因为 numbers 数组只有3个元素,但函数中却取了第4个元素,因此调用 method() 时会抛出异常 ArrayIndexOutOfBoundsException。可是这个异常调用方是没法修正的。

对于这种状况,JVM 特地规定了 RuntimeException 及其子类的这种 UnchekcedException,能够不被 throws 语句声明,编译时不会报错。

异常处理

上面咱们介绍了异常的定义和抛出方式,那么怎么捕获并处理异常呢?这个时候就轮到 try catch finally 出场了。举个例子:

public void readFile(String filePath) throws FileNotFoundException {
    FileReader fr = null;
    BufferedReader br = null;
    try{
        fr = new FileReader(filePath);
        br = new BufferedReader(fr);
        String s = "";
        while((s = br.readLine()) != null){
            System.out.println(s);
        }
    } catch (IOException e) {
        System.out.println("读取文件时出错: " + e.getMessage());
    } finally {
        try {
            br.close();
            fr.close();
        } catch (IOException ex) {
            System.out.println("关闭文件时出错: " + ex.getMessage());
        }
    }
}

这是一个逐行打印文件内容的函数。当输入的 filePath 不存在时,会抛出 CheckedException FileNotFoundException

在文件读取的过程当中,也会出现一些意外状况可能形成一些 IOException,所以代码对可能出现的 IOException 进行了 try catch finally 的处理。 try 代码块中是正常的业务代码, catch 是对异常处理,finally 是不管try 是否异常,都要执行的代码。对于 readFile 这个函数来讲,就是要关闭文件句柄,防止内存泄漏。

这里比较难受的是,因为 fr br 须要在 finally 块中执行,因此必需要在 try 前先声明。有没有优雅一点的写法呢?

这里要介绍一下 JDK 7 推出的新特性:

try-with-resources

try-with-resources 不是一个功能,而是一套让异常捕获语句更加优雅的解决方案。

对于任何实现了 java.io.Closeable 接口的类,只要在 try 后面的()中初始化,JVM 都会自动增长 finally 代码块去执行这些 Closeableclose()方法。

Closable 定义以下:

public interface Closeable extends AutoCloseable {

    /**
     * Closes this stream and releases any system resources associated
     * with it. If the stream is already closed then invoking this
     * method has no effect.
     *
     * <p> As noted in {@link AutoCloseable#close()}, cases where the
     * close may fail require careful attention. It is strongly advised
     * to relinquish the underlying resources and to internally
     * <em>mark</em> the {@code Closeable} as closed, prior to throwing
     * the {@code IOException}.
     *
     * @throws IOException if an I/O error occurs
     */
    public void close() throws IOException;
}

因为 FileReaderBufferedReader 都实现了 Closeable 接口,因此上述前面咱们的 readFile 函数能够改写为:

public void readFile(String filePath) throws FileNotFoundException {
        try(
                FileReader fr = new FileReader(filePath);
                BufferedReader br = new BufferedReader(fr)
        ){
            String s = "";
            while((s = br.readLine()) != null){
                System.out.println(s);
            }
        } catch (IOException e) {
            System.out.println("读取文件时出错: " + e.getMessage());
        }
    }

是否是清爽了许多呢?

最后

欢迎你们关注个人公众号:前程有光,金三银四跳槽面试季,整理了1000多道将近500多页pdf文档的Java面试题资料,文章都会在里面更新,整理的资料也会放在里面