欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java异常详解

程序员文章站 2024-03-14 17:44:10
...

简介

程序运行时,发生的不被期望的事件,它阻止了程序按照程序员的预期正常执行,这就是异常。异常发生时,是任程序自生自灭,立刻退出终止,还是输出错误给用户?或者用C语言风格:用函数返回值作为执行状态?。

Java提供了更加优秀的解决办法: 异常处理机制

异常处理机制: 能让程序在异常发生时,按照代码的预先设定的异常处理逻辑,针对性地处理异常,让程序尽最大可能恢复正常并继续执行,且保持代码的清晰。

Java中的异常可以是函数中的语句执行时引发的,也可以是程序员通过throw 语句手动抛出的,只要在Java程序中产生了异常,就会用一个对应类型的异常对象来封装异常,JRE就会试图寻找异常处理程序来处理异常。

Throwable类是Java异常类型的顶层父类,一个对象只有是 Throwable 类的(直接或者间接)实例,他才是一个异常对象,才能被异常处理机制识别。JDK中内建了一些常用的异常类,我们也可以自定义异常。

Java异常的分类和类结构图

Java标准裤内建了一些通用的异常,这些类以Throwable为顶层父类。

Throwable又派生出Error类和Exception类。

  • 错误:Error类以及他的子类的实例,代表了JVM本身的错误。错误不能被程序员通过代码处理,Error很少出现。因此,程序员应该关注Exception为父类的分支下的各种异常类。

  • 异常:Exception以及他的子类,代表程序运行时发送的各种不期望发生的事件。可以被Java异常处理机制使用,是异常处理的核心。

Java异常详解

总体上我们根据Javac对异常的处理要求,将异常类分为2类。

  • 非检查异常(unckecked exception-运行时异常): Error 和 RuntimeException 以及他们的子类。javac在编译时,不会提示和发现这样的异常,不要求在程序处理这些异常。所以如果愿意,我们可以编写代码处理(使用try…catch…finally)这样的异常,也可以不处理。对于这些异常,我们应该修正代码,而不是去通过异常处理器处理 。这样的异常发生的原因多半是代码写的有问题。如除0错误ArithmeticException,错误的强制类型转换错误ClassCastException,数组索引越界ArrayIndexOutOfBoundsException,使用了空对象NullPointerException等等。(java中所有的运行时异常都会直接或间接继承自RuntimeException。)

  • 检查异常(checked exception-非运行时异常): 除了Error 和 RuntimeException的其它异常。javac强制要求程序员为这样的异常做预备处理工作(使用try…catch…finally或者throws)。在方法中要么用try-catch语句捕获它并处理,要么用throws子句声明抛出它,否则编译不会通过。这样的异常一般是由程序的运行环境导致的。因为程序可能被运行在各种未知的环境下,而程序员无法干预用户如何使用他编写的程序,于是程序员就应该为这样的异常时刻准备着。如SQLException , IOException,ClassNotFoundException 等。(java中凡是继承自Exception而不是继承自RuntimeException的类都时非运行时异常。)

异常处理的基本语法

在编写代码处理异常时,对于检查异常,有2种不同的处理方式:

  • 使用try…catch…finally语句块处理它。
  • 在函数签名中使用throws 声明交给函数调用者caller去解决。

finally块

finally块不管异常是否发生,只要对应的try执行了,则它一定也执行。 只有一种方法让finally块不执行:System.exit()。因此finally块通常用来做资源释放操作:关闭文件,关闭数据库连接等等。

良好的编程习惯是:在try块中打开资源,在finally块中清理释放这些资源。

需要注意的地方:

  • finally块没有处理异常的能力。处理异常的只能是catch块。
  • 在同一try…catch…finally块中 ,如果try中抛出异常,且有匹配的catch块,则先执行catch块,再执行finally块。如果没有catch块匹配,则先执行finally,然后去外面的调用者中寻找合适的catch块。
  • 在同一try…catch…finally块中 ,try发生异常,且匹配的catch块中处理异常时也抛出异常,那么后面的finally也会执行:首先执行finally块,然后去外围调用者中寻找合适的catch块。

自定义异常

  • 如果要自定义异常类,则扩展Exception类即可,因此这样的自定义异常都属于检查异常(checked exception)。

  • 如果要自定义非检查异常,则扩展自RuntimeException。

按照国际惯例,自定义的异常应该总是包含如下的构造函数:

  • 一个无参构造函数
  • 一个带有String参数的构造函数,并传递给父类的构造函数。
  • 一个带有String参数和Throwable参数,并都传递给父类构造函数
  • 一个带有Throwable 参数的构造函数,并传递给父类的构造函数。

特殊案例说明

finally块和return

   public static void main(String[] args) {
        int re = bar();
        System.out.println(re);
    }

    private static int bar() {
        try {
            return 5;
        } finally {
            System.out.println("finally");
        }
    }
    
	/*输出:
    	finally
    */

finally中的return 会覆盖 try 或者catch中的返回值。

	public static void main(String[] args) {
        int result;
        result = foo();
        System.out.println(result);     // 2
        result = bar();
        System.out.println(result);    // 2
    }

    @SuppressWarnings("finally")
    public static int foo() {
        try {
            int a = 5 / 0;
        } catch(Exception e){
            return 1;
        } finally{
            return 2;
        }
    }

    @SuppressWarnings("finally")
    public static int bar() {
        try {
            return 1;
        } finally {
            return 2;
        }
    }

finally中的return会抑制(消灭)前面try或者catch块中的异常

	public static void main(String[] args) {
        int result;
        try {
            result = foo();
            System.out.println(result);           // 输出100
        } catch (Exception e) {
            System.out.println(e.getMessage());    // 没有捕获到异常, 所以这句没执行
        }
        try {
            result = bar();
            System.out.println(result);           // 输出100
        } catch (Exception e) {
            System.out.println(e.getMessage());    // 没有捕获到异常, 所以这句没执行
        }
    }

    //catch中的异常被抑制
    @SuppressWarnings("finally")
    public static int foo() throws Exception {
        try {
            int a = 5 / 0;
            return 1;
        } catch (ArithmeticException amExp) {
            throw new Exception("我将被忽略,因为下面的finally中使用了return");
        } finally {
            return 100;
        }
    }

    //try中的异常被抑制
    @SuppressWarnings("finally")
    public static int bar() throws Exception {
        try {
            int a = 5 / 0;
            return 1;
        } finally {
            return 100;
        }
    }

finally中的异常会覆盖(消灭)前面try或者catch中的异常

	public static void main(String[] args) {
        int result;
        try {
            result = foo();
        } catch (Exception e) {
            System.out.println(e.getMessage());    //输出:我是finaly中的Exception
        }
        try {
            result = bar();
        } catch (Exception e) {
            System.out.println(e.getMessage());    //输出:我是finaly中的Exception
        }
    }

    //catch中的异常被抑制
    @SuppressWarnings("finally")
    public static int foo() throws Exception {
        try {
            int a = 5 / 0;
            return 1;
        } catch (ArithmeticException amExp) {
            throw new Exception("我将被忽略,因为下面的finally中抛出了新的异常");
        } finally {
            throw new Exception("我是finaly中的Exception");
        }
    }

    //try中的异常被抑制
    @SuppressWarnings("finally")
    public static int bar() throws Exception {
        try {
            int a = 5 / 0;
            return 1;
        } finally {
            throw new Exception("我是finaly中的Exception");
        }
    }

上面的几个例子都异于常人的编码思维,因此我建议:

  • 不要在fianlly中使用return。
  • 不要在finally中抛出异常。
  • 减轻finally的任务,不要在finally中做一些其它的事情,finally块仅仅用来释放资源是最合适的。
  • 将尽量将所有的return写在函数的最后面,而不是try … catch … finally中。