全国协议5人面授小班,企业级独立开发考核,转业者的IT软件工程师基地 登录/注册 | 如何报名
当前位置: Java   >  Java 异常处理
admin · 更新于 2021-08-06

Java 异常处理

Java 的异常处理是 Java 语言的一大重要特性,也是提高代码健壮性的最强大方法之一。当我们编写了错误的代码时,编译器在编译期间可能会抛出异常,有时候即使编译正常,在运行代码的时候也可能会抛出异常。本小节我们将介绍什么是异常、Java 中异常类的架构如何进行异常处理如何自定义异常什么是异常链如何使用异常链等内容。

1. 什么是异常

异常就是程序上的错误,我们在编写程序的时候经常会产生错误,这些错误划分为编译期间的错误运行期间的错误。

下面我们来看几个常见的异常案例。

如果语句漏写分号,程序在编译期间就会抛出异常,实例如下:

public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello World!")
    }}
代码块
  • 1
  • 2
  • 3
  • 4
  • 5

运行结果:

$ javac Hello.java
Hello.java:3: 错误: 需要';'
        System.out.println("Hello World!")
                                          ^
1 个错误
代码块
  • 1
  • 2
  • 3
  • 4
  • 5


由于代码的第 3 行语句漏写了分号,Java 编译器给出了明确的提示。

static 关键字写成了 statci,实例如下:

Hello.java:2: 错误: 需要<标识符>
    public statci void main(String[] args) {
                 ^1 个错误
代码块
  • 1
  • 2
  • 3
  • 4

当数组下标越界,程序在编译阶段不会发生错误,但在运行时会抛出异常。实例如下:

public class ArrayOutOfIndex {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3};
        System.out.println(arr[3]);
    }}
代码块
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

运行结果:

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
	at ArrayOutOfIndex.main(ArrayOutOfIndex.java:4)
代码块
  • 1
  • 2


2. Java 异常类架构


1 Error 类

Error 是 Throwable 的一个直接子类,它可以指示合理的应用程序不应该尝试捕获的严重问题。这些错误在应用程序的控制和处理能力之外,编译器不会检查 Error,对于设计合理的应用程序来说,即使发生了错误,本质上也无法通过异常处理来解决其所引起的异常状况。

常见 Error

  • AssertionError:断言错误;

  • VirtualMachineError:虚拟机错误;

  • UnsupportedClassVersionError:Java 类版本错误;

  • OutOfMemoryError :内存溢出错误。

2.3 Exception 类

Exception 是 Throwable 的一个直接子类。它指示合理的应用程序可能希望捕获的条件。

Exception 又包括 Unchecked Exception(非检查异常)和 Checked Exception(检查异常)两大类别。

2.3.1 Unchecked Exception (非检查异常)

Unchecked Exception 是编译器不要求强制处理的异常,包含 RuntimeException 以及它的相关子类。我们编写代码时即使不去处理此类异常,程序还是会编译通过。

常见非检查异常:

  • NullPointerException:空指针异常;

  • ArithmeticException:算数异常;

  • ArrayIndexOutOfBoundsException:数组下标越界异常;

  • ClassCastException:类型转换异常。

2.3.2 Checked Exception(检查异常)

Checked Exception 是编译器要求必须处理的异常,除了 RuntimeException 以及它的子类,都是 Checked Exception 异常。我们在程序编写时就必须处理此类异常,否则程序无法编译通过。

常见检查异常:

  • IOException:IO 异常

  • SQLException:SQL 异常

3. 如何进行异常处理

在 Java 语言中,异常处理机制可以分为两部分:

  1. 抛出异常:当一个方法发生错误时,会创建一个异常对象,并交给运行时系统处理;

  2. 捕获异常:在方法抛出异常之后,运行时系统将转为寻找合适的异常处理器。

Java 通过 5 个关键字来实现异常处理,分别是:throwthrowstrycatchfinally

异常总是先抛出,后捕获的。下面我们将围绕着 5 个关键字来详细讲解如何抛出异常以及如何捕获异常

4. 抛出异常

4.1 实例

我们先来看一个除零异常的实例代码:

public class ExceptionDemo1 {
    // 打印 a / b 的结果
    public static void divide(int a, int b) {
        System.out.println(a / b);
    }

    public static void main(String[] args) {
        // 调用 divide() 方法
        divide(2, 0);
    }}
代码块
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

运行结果:

Exception in thread "main" java.lang.ArithmeticException: / by zero
	at ExceptionDemo1.divide(ExceptionDemo1.java:4)
	at ExceptionDemo1.main(ExceptionDemo1.java:9)
代码块
  • 1
  • 2
  • 3


我们知道 0 是不能用作除数的,由于 divide() 方法中除数 b 为 0,所以代码将停止执行并显示了相关的异常信息,此信息为堆栈跟踪,上面的运行结果告诉我们:main 线程发生了类型为 ArithmeticException 的异常,显示消息为 by zero,并且提示了可能发生异常的方法和行号。

4.2 throw

上面的实例中,程序在运行时引发了错误,那么如何来显示抛出(创建)异常呢?

我们可以使用 throw 关键字来抛出异常,throw 关键字后面跟异常对象,改写上面的实例代码:

public class ExceptionDemo2 {
    // 打印 a / b 的结果
    public static void divide(int a, int b) {
        if (b == 0) {
            // 抛出异常
            throw new ArithmeticException("除数不能为零");
        }
        System.out.println(a / b);
    }

    public static void main(String[] args) {
        // 调用 divide() 方法
        divide(2, 0);
    }}
代码块
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15


Exception in thread "main" java.lang.ArithmeticException: 除数不能为零
	at ExceptionDemo2.divide(ExceptionDemo2.java:5)
	at ExceptionDemo2.main(ExceptionDemo2.java:12)
代码块
  • 1
  • 2
  • 3


代码在运行时同样引发了错误,但显示消息为 “除数不能为零”。我们看到 divide() 方法中加入了条件判断,如果调用者将参数 b 设置为 0 时,会使用 throw 关键字来抛出异常,throw 后面跟了一个使用 new 关键字实例化的算数异常对象,并且将消息字符串作为参数传递给了算数异常的构造函数。

我们可以使用 throw 关键字抛出任何类型的 Throwable 对象,它会中断方法,throw 语句之后的所有内容都不会执行。除非已经处理抛出的异常。异常对象不是从方法中返回的,而是从方法中抛出的。

4.3 throws

可以通过 throws 关键字声明方法要抛出何种类型的异常。如果一个方法可能会出现异常,但是没有能力处理这种异常,可以在方法声明处使用 throws 关键字来声明要抛出的异常。例如,汽车在运行时可能会出现故障,汽车本身没办法处理这个故障,那就让开车的人来处理。

throws 用在方法定义时声明该方法要抛出的异常类型,如下是伪代码:

public void demoMethod() throws Exception1, Exception2, ... ExceptionN {
    // 可能产生异常的代码}
代码块
  • 1
  • 2
  • 3

throws 后面跟的异常类型列表可以有一个也可以有多个,多个则以 , 分割。当方法产生异常列表中的异常时,将把异常抛向方法的调用方,由调用方处理。

throws 有如下使用规则:

  1. 如果方法中全部是非检查异常(即 ErrorRuntimeException 以及的子类),那么可以不使用 throws 关键字来声明要抛出的异常,编译器能够通过编译,但在运行时会被系统抛出;
  2. 如果方法中可能出现检查异常,就必须使用 throws 声明将其抛出或使用 try catch 捕获异常,否则将导致编译错误;
  3. 当一个方法抛出了异常,那么该方法的调用者必须处理或者重新抛出该异常;
  4. 当子类重写父类抛出异常的方法时,声明的异常必须是父类所声明异常的同类或子类。

5. 捕获异常

使用 try 和 catch 关键字可以捕获异常。try catch 代码块放在异常可能发生的地方。它的语法如下:

try {
    // 可能会发生异常的代码块} catch (Exception e1) {
    // 捕获并处理try抛出的异常类型Exception} catch (Exception2 e2) {
    // 捕获并处理try抛出的异常类型Exception2} finally {
    // 无论是否发生异常,都将执行的代码块}
代码块
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

我们来看一下上面语法中的 3 种语句块:

  1. try 语句块:用于监听异常,当发生异常时,异常就会被抛出;
  2. catch 语句块catch 语句包含要捕获的异常类型的声明,当 try 语句块发生异常时,catch 语句块就会被检查。当 catch 块尝试捕获异常时,是按照 catch 块的声明顺序从上往下寻找的,一旦匹配,就不会再向下执行。因此,如果同一个 try 块下的多个 catch 异常类型有父子关系,应该将子类异常放在前面,父类异常放在后面;
  3. finally 语句块:无论是否发生异常,都会执行 finally 语句块。finally 常用于这样的场景:由于 finally 语句块总是会被执行,所以那些在 try 代码块中打开的,并且必须回收的物理资源(如数据库连接、网络连接和文件),一般会放在 finally 语句块中释放资源。

try 语句块后可以接零个或多个 catch 语句块,如果没有 catch 块,则必须跟一个 finally 语句块。简单来说,try 不允许单独使用,必须和 catch 或 finally 组合使用,catch 和 finally 也不能单独使用。


为什么选择汉码未来