什么是 Java 异常处理?

混世魔王
• 阅读 396

本文首发自「慕课网」,想了解更多IT干货内容,程序员圈内热闻,欢迎关注!

作者| 慕课网精英讲师 ColorfulC

Java 的异常处理是 Java 语言的一大重要特性,也是提高代码健壮性的最强大方法之一。当我们编写了错误的代码时,编译器在编译期间可能会抛出异常,有时候即使编译正常,在运行代码的时候也可能会抛出异常。本篇文章我们将介绍什么是异常、Java 中异常类的架构、如何进行异常处理、如何自定义异常、什么是异常链、如何使用异常链等内容。1. 什么是异常异常就是程序上的错误,我们在编写程序的时候经常会产生错误,这些错误划分为编译期间的错误和运行期间的错误。下面我们来看几个常见的异常案例。如果语句漏写分号,程序在编译期间就会抛出异常,实例如下:public class Hello {

public static void main(String[] args) {
    System.out.println("Hello World!")
}

}
代码块12345运行结果:$ javac Hello.java
Hello.java:3: 错误: 需要';'

    System.out.println("Hello World!")
                                      ^

1 个错误
代码块12345运行过程:
什么是 Java 异常处理?
由于代码的第 3 行语句漏写了分号,Java 编译器给出了明确的提示。static 关键字写成了 statci,实例如下:Hello.java:2: 错误: 需要<标识符>

public statci void main(String[] args) {
             ^

1 个错误
代码块1234当数组下标越界,程序在编译阶段不会发生错误,但在运行时会抛出异常。实例如下:public class ArrayOutOfIndex {

public static void main(String[] args) {
    int[] arr = {1, 2, 3};
    System.out.println(arr[3]);
}

}
代码块123456运行结果:Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3

at ArrayOutOfIndex.main(ArrayOutOfIndex.java:4)

代码块12运行过程:
什么是 Java 异常处理?

  1. Java 异常类架构在 Java 中,通过 Throwable 及其子类来描述各种不同类型的异常。如下是 Java 异常类的架构图(不是全部,只展示部分类):
    什么是 Java 异常处理?
    2.1 Throwable 类Throwable 位于 java.lang 包下,它是 Java 语言中所有错误(Error)和异常(Exception)的父类。Throwable 包含了其线程创建时线程执行堆栈的快照,它提供了 printStackTrace() 等接口用于获取堆栈跟踪数据等信息。主要方法:fillInStackTrace: 用当前的调用栈层次填充 Throwable 对象栈层次,添加到栈层次任何先前信息中;getMessage:返回关于发生的异常的详细信息。这个消息在 Throwable 类的构造函数中初始化了;getCause:返回一个 Throwable 对象代表异常原因;getStackTrace:返回一个包含堆栈层次的数组。下标为 0 的元素代表栈顶,最后一个元素代表方法调用堆栈的栈底;printStackTrace:打印 toString() 结果和栈层次到 System.err,即错误输出流。2.2 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 语言中,异常处理机制可以分为两部分:抛出异常:当一个方法发生错误时,会创建一个异常对象,并交给运行时系统处理;捕获异常:在方法抛出异常之后,运行时系统将转为寻找合适的异常处理器。Java 通过 5 个关键字来实现异常处理,分别是:throw、throws、try、catch、finally。异常总是先抛出,后捕获的。下面我们将围绕着 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);

    }
    }
    代码块1234567891011运行结果:Exception in thread "main" java.lang.ArithmeticException: / by zero
    at ExceptionDemo1.divide(ExceptionDemo1.java:4)
    at ExceptionDemo1.main(ExceptionDemo1.java:9)
    代码块123运行过程:
    什么是 Java 异常处理?
    我们知道 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);

    }
    }
    代码块123456789101112131415运行结果:Exception in thread "main" java.lang.ArithmeticException: 除数不能为零
    at ExceptionDemo2.divide(ExceptionDemo2.java:5)
    at ExceptionDemo2.main(ExceptionDemo2.java:12)
    代码块123运行过程:
    什么是 Java 异常处理?
    代码在运行时同样引发了错误,但显示消息为 “除数不能为零”。我们看到 divide() 方法中加入了条件判断,如果调用者将参数 b 设置为 0 时,会使用 throw 关键字来抛出异常,throw 后面跟了一个使用 new 关键字实例化的算数异常对象,并且将消息字符串作为参数传递给了算数异常的构造函数。我们可以使用 throw 关键字抛出任何类型的 Throwable 对象,它会中断方法,throw 语句之后的所有内容都不会执行。除非已经处理抛出的异常。异常对象不是从方法中返回的,而是从方法中抛出的。4.3 throws可以通过 throws 关键字声明方法要抛出何种类型的异常。如果一个方法可能会出现异常,但是没有能力处理这种异常,可以在方法声明处使用 throws 关键字来声明要抛出的异常。例如,汽车在运行时可能会出现故障,汽车本身没办法处理这个故障,那就让开车的人来处理。throws 用在方法定义时声明该方法要抛出的异常类型,如下是伪代码:public void demoMethod() throws Exception1, Exception2, ... ExceptionN {
    // 可能产生异常的代码
    }
    代码块123throws 后面跟的异常类型列表可以有一个也可以有多个,多个则以 , 分割。当方法产生异常列表中的异常时,将把异常抛向方法的调用方,由调用方处理。throws 有如下使用规则:如果方法中全部是非检查异常(即 Error、RuntimeException 以及的子类),那么可以不使用 throws 关键字来声明要抛出的异常,编译器能够通过编译,但在运行时会被系统抛出;如果方法中可能出现检查异常,就必须使用 throws 声明将其抛出或使用 try catch 捕获异常,否则将导致编译错误;当一个方法抛出了异常,那么该方法的调用者必须处理或者重新抛出该异常;当子类重写父类抛出异常的方法时,声明的异常必须是父类所声明异常的同类或子类。5. 捕获异常使用 try 和 catch 关键字可以捕获异常。try catch 代码块放在异常可能发生的地方。它的语法如下:try {
    // 可能会发生异常的代码块
    } catch (Exception e1) {
    // 捕获并处理try抛出的异常类型Exception
    } catch (Exception2 e2) {
    // 捕获并处理try抛出的异常类型Exception2
    } finally {
    // 无论是否发生异常,都将执行的代码块
    }
    代码块123456789我们来看一下上面语法中的 3 种语句块:try 语句块:用于监听异常,当发生异常时,异常就会被抛出;catch 语句块:catch 语句包含要捕获的异常类型的声明,当 try 语句块发生异常时,catch 语句块就会被检查。当 catch 块尝试捕获异常时,是按照 catch 块的声明顺序从上往下寻找的,一旦匹配,就不会再向下执行。因此,如果同一个 try 块下的多个 catch 异常类型有父子关系,应该将子类异常放在前面,父类异常放在后面;finally 语句块:无论是否发生异常,都会执行 finally 语句块。finally 常用于这样的场景:由于 finally 语句块总是会被执行,所以那些在 try 代码块中打开的,并且必须回收的物理资源(如数据库连接、网络连接和文件),一般会放在 finally 语句块中释放资源。try 语句块后可以接零个或多个 catch 语句块,如果没有 catch 块,则必须跟一个 finally 语句块。简单来说,try 不允许单独使用,必须和 catch 或 finally 组合使用,catch 和 finally 也不能单独使用。实例如下:public class ExceptionDemo3 {
    // 打印 a / b 的结果
    public static void divide(int a, int b) {

     System.out.println(a / b);

    }

    public static void main(String[] args) {

     try {
         // try 语句块
         // 调用 divide() 方法
         divide(2, 0);
     } catch (ArithmeticException e) {
         // catch 语句块
         System.out.println("catch: 发生了算数异常:" + e);
     } finally {
         // finally 语句块
         System.out.println("finally: 无论是否发生异常,都会执行");
     }

    }
    }
    代码块1234567891011121314151617181920运行结果:catch: 发生了算数异常:java.lang.ArithmeticException: / by zero
    finally: 无论是否发生异常,都会执行
    代码块12运行过程:
    什么是 Java 异常处理?
    divide() 方法中除数 b 为 0,会发生除零异常,我们在方法调用处使用了 try 语句块对异常进行捕获;如果捕获到了异常, catch 语句块会对 ArithmeticException 类型的异常进行处理,此处打印了一行自定义的提示语句;最后的 finally 语句块,无论发生异常与否,总会执行。Java 7 以后,catch 多种异常时,也可以像下面这样简化代码:try {
    // 可能会发生异常的代码块
    } catch (Exception | Exception2 e) {
    // 捕获并处理try抛出的异常类型
    } finally {
    // 无论是否发生异常,都将执行的代码块
    }
    代码块12345676. 自定义异常自定义异常,就是定义一个类,去继承 Throwable 类或者它的子类。Java 内置了丰富的异常类,通常使用这些内置异常类,就可以描述我们在编码时出现的大部分异常情况。一旦内置异常无法满足我们的业务要求,就可以通过自定义异常描述特定业务产生的异常类型。实例:public class ExceptionDemo4 {

    static class MyCustomException extends RuntimeException {

     /**
      * 无参构造方法
      */
     public MyCustomException() {
         super("我的自定义异常");
     }

    }

    public static void main(String[] args) {

       // 直接抛出异常
     throw new MyCustomException();

    }
    }
    代码块12345678910111213141516运行结果:Exception in thread "main" ExceptionDemo4$MyCustomException: 我的自定义异常
    at ExceptionDemo4.main(ExceptionDemo4.java:13)
    代码块12运行过程:
    什么是 Java 异常处理?
    在代码中写了一个自定义异常 MyCustomException,继承自 RuntimeException,它是一个静态内部类,这样在主方法中就可以直接抛出这个异常类了。当然,也可以使用 catch 来捕获此类型异常。7. 异常链异常链是以一个异常对象为参数构造新的异常对象,新的异常对象将包含先前异常的信息。简单来说,就是将异常信息从底层传递给上层,逐层抛出,我们来看一个实例:public class ExceptionDemo5 {

    /**

    • 第一个自定义的静态内部异常类
      */

    static class FirstCustomException extends Exception {

     // 无参构造方法
     public FirstCustomException() {
         super("第一个异常");
     }

    }

    /**

    • 第二个自定义的静态内部异常类
      */

    static class SecondCustomException extends Exception {

     public SecondCustomException() {
         super("第二个异常");
     }

    }

    /**

    • 第三个自定义的静态内部异常类
      */

    static class ThirdCustomException extends Exception {

     public ThirdCustomException() {
         super("第三个异常");
     }

    }

    /**

    • 测试异常链静态方法1,直接抛出第一个自定义的静态内部异常类
    • @throws FirstCustomException
      */

    public static void f1() throws FirstCustomException {

     throw new FirstCustomException();

    }

    /**

    • 测试异常链静态方法2,调用f1()方法,并抛出第二个自定义的静态内部异常类
    • @throws SecondCustomException
      */

    public static void f2() throws SecondCustomException {

     try {
         f1();
     } catch (FirstCustomException e) {
         throw new SecondCustomException();
     }

    }

    /**

    • 测试异常链静态方法3,调用f2()方法, 并抛出第三个自定义的静态内部异常类
    • @throws ThirdCustomException
      */

    public static void f3() throws ThirdCustomException {

     try {
         f2();
     } catch (SecondCustomException e) {
         throw new ThirdCustomException();
     }

    }

    public static void main(String[] args) throws ThirdCustomException {

     // 调用静态方法f3()
     f3();

    }
    }
    代码块12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970运行结果:Exception in thread "main" ExceptionDemo5$ThirdCustomException: 第三个异常
    at ExceptionDemo5.f3(ExceptionDemo5.java:46)
    at ExceptionDemo5.main(ExceptionDemo5.java:51)
    代码块123运行过程:
    什么是 Java 异常处理?
    通过运行结果,我们只获取到了静态方法 f3() 所抛出的异常堆栈信息,前面代码所抛出的异常并没有被显示。我们改写上面的代码,让异常信息以链条的方式 “连接” 起来。可以通过改写自定义异常的构造方法,来获取到之前异常的信息。实例如下:/**

    • @author colorful@TaleLin
      */

    public class ExceptionDemo6 {

    /**

    • 第一个自定义的静态内部异常类
      */

    static class FirstCustomException extends Exception {

     // 无参构造方法
     public FirstCustomException() {
         super("第一个异常");
     }
    

    }

    /**

    • 第二个自定义的静态内部异常类
      */

    static class SecondCustomException extends Exception {

     /**
      * 通过构造方法获取之前异常的信息
      * @param cause 捕获到的异常对象
      */
     public SecondCustomException(Throwable cause) {
         super("第二个异常", cause);
     }

    }

    /**

    • 第三个自定义的静态内部异常类
      */

    static class ThirdCustomException extends Exception {

     /**
      * 通过构造方法获取之前异常的信息
      * @param cause 捕获到的异常对象
      */
     public ThirdCustomException(Throwable cause) {
         super("第三个异常", cause);
     }

    }

    /**

    • 测试异常链静态方法1,直接抛出第一个自定义的静态内部异常类
    • @throws FirstCustomException
      */

    public static void f1() throws FirstCustomException {

     throw new FirstCustomException();

    }

    /**

    • 测试异常链静态方法2,调用f1()方法,并抛出第二个自定义的静态内部异常类
    • @throws SecondCustomException
      */

    public static void f2() throws SecondCustomException {

     try {
         f1();
     } catch (FirstCustomException e) {
         throw new SecondCustomException(e);
     }

    }

    /**

    • 测试异常链静态方法3,调用f2()方法, 并抛出第三个自定义的静态内部异常类
    • @throws ThirdCustomException
      */

    public static void f3() throws ThirdCustomException {

     try {
         f2();
     } catch (SecondCustomException e) {
         throw new ThirdCustomException(e);
     }

    }

    public static void main(String[] args) throws ThirdCustomException {

     // 调用静态方法f3()
     f3();

    }
    }
    代码块12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182运行结果:Exception in thread "main" ExceptionDemo6$ThirdCustomException: 第三个异常
    at ExceptionDemo6.f3(ExceptionDemo6.java:74)
    at ExceptionDemo6.main(ExceptionDemo6.java:80)
    Caused by: ExceptionDemo6$SecondCustomException: 第二个异常
    at ExceptionDemo6.f2(ExceptionDemo6.java:62)
    at ExceptionDemo6.f3(ExceptionDemo6.java:72)
    ... 1 more
    Caused by: ExceptionDemo6$FirstCustomException: 第一个异常
    at ExceptionDemo6.f1(ExceptionDemo6.java:51)
    at ExceptionDemo6.f2(ExceptionDemo6.java:60)
    ... 2 more
    代码块1234567891011运行过程:
    什么是 Java 异常处理?
    通过运行结果,我们看到,异常发生的整个过程都打印到了屏幕上,这就是一个异常链。8. 小结通过本篇文章,我们知道了异常就是程序上的错误,良好的异常处理可以提高代码的健壮性。Java 语言中所有错误(Error)和异常(Exception)的父类都是 Throwable。Error 和 Exception 是 Throwable 的直接子类,我们通常说的异常处理实际上就是处理 Exception 及其子类,异常又分为检查型异常和非检查型异常。通过抛出异常和捕获异常来实现异常处理。我们亦可以通过继承 Throwable 类或者它的子类来自定义异常类。通过构造方法获取之前异常的信息可以实现异常链。

欢迎关注「慕课网」,发现更多IT圈优质内容,分享干货知识,帮助你成为更好的程序员!

点赞
收藏
评论区
推荐文章
如何优雅的处理异常
Java语言按照错误严重性,从throwale根类衍生出Error和Exception两大派系。本文从异常的定义、处理异常的方式、如何优雅的抛出异常以及处理异常等方面来聊聊如何异常这件事
Wesley13 Wesley13
3年前
java面试题 IO GC BAT面试题系列 基础篇(十五)
请问运行时异常与受检异常有什么区别?考察点:异常参考回答:异常表示程序运行过程中可能出现的非正常状态,运行时异常表示虚拟机的通常操作中可能遇到的异常,是一种常见运行错误,只要程序设计得没有问题通常就不会发生。受检异常跟程序运行的上下文环境有关,即使程序设计无误,仍然可能因使用的问题而引发。Java编译
Wesley13 Wesley13
3年前
初探 Objective
作者:Cyandev,iOS和MacOS开发者,目前就职于字节跳动0x00前言异常处理是许多高级语言都具有的特性,它可以直接中断当前函数并将控制权转交给能够处理异常的函数。不同语言在异常处理的实现上各不相同,本文主要来分析一下ObjectiveC和C这两个语言。为什么要把ObjectiveC和
Wesley13 Wesley13
3年前
Java异常
异常分为两种:Exception、ErrorException:异常,可以捕捉到,进行处理以后可以让程序继续正常执行Error:错误,不能捕捉,只能修改代码,重新执行ThrowableException(RuntimeException非运行时异常)throw:抛出指定的异常throws:用在方法声明处,声明该方法可能发生
Wesley13 Wesley13
3年前
JAVA运行时异常及常见的5中RuntimeExecption
java运行时异常是可能在java虚拟机正常工作时抛出的异常。java提供了两种异常机制。一种是运行时异常(RuntimeExepction),一种是检查式异常(checkedexecption)。检查式异常:我们经常遇到的IO异常及sql异常就属于检查式异常。对于这种异常,java编译器要求我们必须对出现的这些异常进行catch所以面对这种异
Wesley13 Wesley13
3年前
JAVA学习笔记 之 异常
异常的概述代码在运行时期发生的问题就称之为异常。在java中,异常被封装成为了一个类,当程序出现了异常类中的问题时,异常的信息(如位置和原因)就会被抛出。在java中用Exception来描述异常,publicclassExceptionextendsThrowable则是异常的继承体系。 Throwable是所有错误或异常的
Wesley13 Wesley13
3年前
Java入门之异常处理
1.异常概念异常指的是程序在执行过程中出现的非正常的情况,导致JVM的非正常停止。在Java中,异常是一个类,产生异常就是创建异常对象并抛出一个异常对象。异常指的并不是语法错误,语法错误,编译不会通过,而是编译通过后,程序执行异常。异常的作用是帮助我们找到程序中的问题。2.异常产生过程解析pub
Wesley13 Wesley13
3年前
Java异常处理的最佳实践
Java异常处理的最佳实践为什么要有最佳实践我们在写程序是不可避免的要对代码进行异常处理,但是有时对异常的处理会使我们的程序变的更加糟糕,这是我们所不想看到的。所以,我们再进行异常处理时需要遵循一定的套路,来降低异常处理对我们程序的影响。异常产生的原因一般来说,java中的异常会
Wesley13 Wesley13
3年前
Java异常架构
Java异常简介Java异常是Java提供的一种识别及响应错误的一致性机制。Java异常机制可以使程序中异常处理代码和正常业务代码分离,保证程序代码更加优雅,并提高程序健壮性。在有效使用异常的情况下,异常能清晰的回答what,where,why这3个问题:异常类型回答了“什么”被抛出,异常堆栈跟踪回答了“在哪“抛出,异常信息
小万哥 小万哥
2年前
C++异常和错误处理机制:如何使您的程序更加稳定和可靠
在C编程中,异常处理和错误处理机制是非常重要的。它们可以帮助程序员有效地处理运行时错误和异常情况。本文将介绍C中的异常处理和错误处理机制。什么是异常处理?异常处理是指在程序执行过程中发生异常或错误时,程序能够捕获并处理这些异常或错误的机制。例如,当
小万哥 小万哥
1年前
Java 异常处理与正则表达式详解,实例演练及最佳实践
Java异常Try...Catch在Java代码执行期间,可能会发生各种错误,包括程序员编码错误、用户输入错误以及其他不可预料的状况。当错误发生时,Java通常会停止并生成错误消息,这个过程称为抛出异常。try...catch语句try语句允许您定义一段代