Java 异常机制详解

一 Java 异常概述

定义:Java 异常是 Java 提供的一种识别及响应错误的一致性机制

作用:Java 异常机制可以使程序中异常处理代码和正常业务代码分离,保证程序代码更加优雅,并提高程序健壮性。在有效使用异常的情况下,异常能清晰的回答 what, where, why 这3个问题:异常类型回答了“什么”被抛出,异常堆栈跟踪回答了“在哪“抛出,异常信息回答了“为什么“会抛出。

二 Java 异常分类

异常是一个事件,它发生在程序运行期间,干扰了正常的指令流程。Java 通 过 API 中 Throwable 类的众多子类描述各种不同的异常。因而,Java 异常都是对象,是 Throwable 子类的实例,描述了出现在一段编码中的错误条件。当条件满足时,错误将引发异常。

Java 异常的继承层次结构:

图片中我们看到 Java 异常的基类是 Throwable 类型,然后它有两个子类 Error(错误)和 Exception(异常)类型,然后 Exception 类又分为运行时异常(RuntimeException)及非运行时异常(编译异常)。以下将分别介绍。

2.1 Error(错误)

Error 一般表示编译时或者系统错误,例如:Virtual MachineError(虚拟机运行错误),StackOverflowError:栈溢出错误、OutOfMemoryError:内存不足错误等。此类错误开发者无法处理且不可恢复,JVM 将终止线程,因此不应该实现任何新的 Error 子类。

2.2 Exception(异常)

程序本身可以捕获并且可以处理的异常,也就是要么捕获异常并作出处理,要么继续抛出异常。Exception 这种异常又分为两类:运行时异常和非运行时异常。

2.2.1 运行时异常(非受检查异常)

RuntimeException 及其子类都统称为运行时异常,例如:NullPointExecrption(空指针)、ArithmeticException(算术错误)、ArrayIndexOutOfBoundsException(数组越界)等。

这类异常是非受检查的,具体根据需要来判断是否需要捕获和处理,并不会在编译期强制要求。开发者应该从程序编码阶段就尽可能避免其发生。

2.2.2 非运行时异常(受检查异常)

RuntimeException 以外的异常都属于非运行时异常。例如 IOException、SQLException 等以及用户自定义的 Exception 异常等。

这类异常是受检查的,在源代码里必须显式地进行捕获处理,是编译期检查的一部分。这种异常要么用 try-catch 语句捕获它,要么用 throws 子句声明抛出它,否则编译不会通过。

三 Java 异常处理

3.1 异常关键字

  • try – 用于监听。将要被监听的代码(可能抛出异常的代码)放在try语句块之内,当 try 语句块内发生异常时,异常就被抛出。
  • catch – 用于捕获异常。catch用来捕获try语句块中发生的异常。
  • finally – 为确保一段代码不管发生什么异常状况都要被执行。主要用于回收在 try 块里打开的物理资源(如数据库连接、网络连接和磁盘文件)。
  • throw – 用来明确地抛出一个异常。
  • throws – 用来声明一个方法可能抛出的各种异常。

3.2 Java 内置异常

非受检查异常
异常描述
ArithmeticException当出现异常的运算条件时,抛出此异常。例如,一个整数"除以零"时,抛出此类的一个实例。
ArrayIndexOutOfBoundsException用非法索引访问数组时抛出的异常。如果索引为负或大于等于数组大小,则该索引为非法索引。
ArrayStoreException试图将错误类型的对象存储到一个对象数组时抛出的异常。
ClassCastException当试图将对象强制转换为不是实例的子类时,抛出该异常。
IllegalArgumentException抛出的异常表明向方法传递了一个不合法或不正确的参数。
IllegalMonitorStateException抛出的异常表明某一线程已经试图等待对象的监视器,或者试图通知其他正在等待对象的监视器而本身没有指定监视器的线程。
IllegalStateException在非法或不适当的时间调用方法时产生的信号。换句话说,即 Java 环境或 Java 应用程序没有处于请求操作所要求的适当状态下。
IllegalThreadStateException线程没有处于请求操作所要求的适当状态时抛出的异常。
IndexOutOfBoundsException指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。
NegativeArraySizeException如果应用程序试图创建大小为负的数组,则抛出该异常。
NullPointerException当应用程序试图在需要对象的地方使用 null 时,抛出该异常
NumberFormatException当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。
SecurityException由安全管理器抛出的异常,指示存在安全侵犯。
StringIndexOutOfBoundsException此异常由 String 方法抛出,指示索引或者为负,或者超出字符串的大小。
UnsupportedOperationException当不支持请求的操作时,抛出该异常。
受检查异常
异常描述
ClassNotFoundException应用程序试图加载类时,找不到相应的类,抛出该异常。
CloneNotSupportedException当调用 Object 类中的 clone 方法克隆对象,但该对象的类无法实现 Cloneable 接口时,抛出该异常。
IllegalAccessException拒绝访问一个类的时候,抛出该异常。
InstantiationException当试图使用 Class 类中的 newInstance 方法创建一个类的实例,而指定的类对象因为是一个接口或是一个抽象类而无法实例化时,抛出该异常。
InterruptedException一个线程被另一个线程中断,抛出该异常。
NoSuchFieldException请求的变量不存在
NoSuchMethodException请求的方法不存在

3.3 异常处理方法

Throwable 类主要方法

序号方法及说明
1public String getMessage()
返回关于发生的异常的详细信息。这个消息在 Throwable 类的构造函数中初始化了。
2public Throwable getCause()
返回一个 Throwable 对象代表异常原因。
3public String toString()
返回此 Throwable 的简短描述。
4public void printStackTrace()
将此 Throwable 及其回溯打印到标准错误流。。
5public StackTraceElement [] getStackTrace()
返回一个包含堆栈层次的数组。下标为 0 的元素代表栈顶,最后一个元素代表方法调用堆栈的栈底。
6public Throwable fillInStackTrace()
用当前的调用栈层次填充 Throwable 对象栈层次,添加到栈层次任何先前信息中。

3.5 异常的声明(throws)

当一个方法产生一个它不处理的异常时,那么就需要在该方法的头部声明这个异常,以便将该异常传递到方法的外部进行处理。使用 throws 声明的方法表示此方法不处理异常,如果有多个异常类,它们之间用逗号分隔。throws 具体格式如下:

public void test()throws NullPointerException,ParseException{
    //something statements
}

注意:若是父类的方法没有声明异常,则子类继承方法后,也不能声明异常。

3.6 异常的抛出(throw)

与 throws 不同的是,throw 语句用来直接拋出一个异常,后接一个可拋出的异常类对象,其语法格式如下:

throw ExceptionObject;

其中,ExceptionObject 必须是 Throwable 类或其子类的对象。如果是自定义异常类,也必须是 Throwable 的直接或间接子类。
大部分情况下都不需要手动抛出异常,因为 Java 的大部分方法要么已经处理异常,要么已声明异常。所以一般都是捕获异常或者再往上抛。

3.7 异常的自定义

在 Java 中可以自定义异常。编写自定义异常类时需要注意以下几点。

  • 所有异常都必须是 Throwable 的子类。
  • 如果希望自定义一个检查性异常类,则需继承 Exception 类。
  • 如果希望自定义一个运行时异常类,则需要继承 RuntimeException 类。
    可以像下面这样定义自己的异常类:
public class MyException extends Exception {
    public MyException(){ }
    public MyException(String msg){
        super(msg);
    }
    // ...
}

3.8 异常的捕获

异常捕获处理的方法通常有:

  • try-catch
  • try-catch-finally
  • try-finally
  • try-with-resource
try-catch

在一个 try-catch 语句块中可以捕获多个异常类型,并对不同类型的异常做出不同的处理

try{
   // 程序代码
}catch(异常类型1 异常的变量名1){
  // 程序代码
}catch(异常类型2 异常的变量名2){
  // 程序代码
}catch(异常类型3 异常的变量名3){
  // 程序代码
}

同一个 catch 也可以捕获多种类型异常,用 | 隔开

try{
   // 程序代码
}catch(异常类型1 | 异常类型2 异常的变量名1){
  // 程序代码
}catch(异常类型3 异常的变量名2){
  // 程序代码
}
try-catch-finally

finally 关键字用来创建在 try 代码块后面执行的代码块。无论是否发生异常,finally 代码块中的代码总会被执行。在 finally 代码块中,可以运行清理类型等收尾善后性质的语句。

try{
  // 程序代码
}catch(异常类型1 异常的变量名1){
  // 程序代码
}catch(异常类型2 异常的变量名2){
  // 程序代码
}finally{
  // 程序代码
}
  • 执行的顺序
    • 当 try 没有捕获到异常时:try 语句块中的语句逐一被执行,程序将跳过 catch 语句块,执行 finally 语句块和其后的语句;
    • 当 try 捕获到异常,catch 语句块里没有处理此异常的情况:当 try 语句块里的某条语句出现异常时,而没有处理此异常的 catch 语句块时,此异常将会抛给 JVM 处理,finally 语句块里的语句还是会被执行,但 finally 语句块后的语句不会被执行;
    • 当 try 捕获到异常,catch 语句块里有处理此异常的情况:在 try 语句块中是按照顺序来执行的,当执行到某一条语句出现异常时,程序将跳到 catch 语句块,并与 catch 语句块逐一匹配,找到与之对应的处理程序,其他的 catch 语句块将不会被执行,而 try 语句块中,出现异常之后的语句也不会被执行,catch 语句块执行完后,执行 finally 语句块里的语句,最后执行 finally 语句块后的语句;
try-finally

可以直接用 try-finally 吗? 可以。
try 块中引起异常,异常代码之后的语句不再执行,直接执行 finally 语句。try 块没有引发异常,则执行完 try 块就执行 finally 语句。

try-finally 可用在不需要捕获异常的代码,可以保证资源在使用后被关闭。例如 IO 流中执行完相应操作后,关闭相应资源;使用 Lock 对象保证线程同步,通过 finally 可以保证锁会被释放;数据库连接代码时,关闭连接操作等等。

//以 Lock 加锁为例,演示 try-finally
ReentrantLock lock = new ReentrantLock();
try {
    //需要加锁的代码
} finally {
    lock.unlock(); //保证锁一定被释放
}

finally 遇见如下情况不会执行

  • 在前面的代码中用了 System.exit()退出程序。
  • finally 语句块中发生了异常。
  • 程序所在的线程死亡。
  • 关闭 CPU。
try-with-resource

JDK7 之后,Java 新增的 try-with-resource 语法糖来打开资源,并且可以在语句执行完毕后确保每个资源都被自动关闭 。任何实现了 java.lang.AutoCloseable 的对象, 包括所有实现了 java.io.Closeable 的对象, 都可以用作一个资源。以下为代码实现

public class MyAutoClosable implements AutoCloseable {
    public void doIt() {
        System.out.println("MyAutoClosable doing it!");
    }
    @Override
    public void close() throws Exception {
        System.out.println("MyAutoClosable closed!");
    }
    public static void main(String[] args) {
        try(MyAutoClosable myAutoClosable = new MyAutoClosable()){
            myAutoClosable.doIt();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

try 代码块退出时,会自动调用 myAutoClosable.close 方法。这个好处在于,我们不用再去关心连接调用完了是否关闭,文件读写完了是否关闭,专心的实现业务即可。

3.9 异常使用建议

  1. 尽量不要捕获类似 Exception 这样的通用异常,而是应该捕获特定异常。
  2. 不要生吞(swallow)异常。这是异常处理中要特别注意的事情,因为很可能会导致非常难以诊断的诡异情况。
  3. 不要在 finally 代码块中处理返回值。
  4. 不要在 try 代码块中调用 return、break 或 continue 语句。万一无法避免,一定要确保 finally 的存在不会改变函数的返回值。
  5. 勿将异常用于控制流。

四 参考文献

Java 基础 - 异常机制详解

深入理解Java异常

Java 核心技术面试精讲

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值