Java异常

1、什么叫异常

先来看个例子

public class ExceptionDemo {
    public static void main(String[] args) {
        int x = 6 / 0;
        System.out.println("x = " + x);
        System.out.println("程序正常结束。");
    }
}

当运行这段代码时,控制台输出如下内容,我们看到第4行的打印语句没有被执行

Exception in thread "main" java.lang.ArithmeticException: / by zero
    at ExceptionDemo.main(ExceptionDemo.java:3)

根据输出的内容可以得知是代码的第三行出了问题导致程序无法正常继续执行,原因是Exception in thread "main" java.lang.ArithmeticException: / by zero,意思是说出现了算数异常,由于0,这是因为在数学中规定除数不能为0。这里的java.lang.ArithmeticException是Java中定义的一个异常类,用来专门处理数学运算中出现的问题,像这种在程序中阻止当前方法继续执行的问题,就叫做异常,异常发生的原因有很多,比如说上面的数学运算中的除数为0,又如我们操作IO流时打开了不存在的文件时,则会抛出java.io.FileNotFoundException异常,还有一些则是因为用户输入了非法的数据而造成的。

2、异常的分类

因为java是一门面向对象的语言,所以异常也被java的设计者们封装成了对象,并定义了一个基类java.lang.Throwable作为所有异常的超类。在Java API中已经定义了许多异常类,这些异常类分为两大类,错误Error和异常Exception。下图很好的展示了java中异常的体系结构:

 

首先说明一点,java中的Exception类的子类不仅仅只是像上图所示只包含IOExceptionRuntimeException这两大类,事实上Exception的子类很多很多(可通过查询API)。从图中可以看出所有异常类型都是内置类Throwable的子类,因而Throwable在异常类的层次结构的顶层,接着便分成了2个派系,一个是Error,另一个是Exception,分别表示错误和异常。其中异常类Exception又分为运行时异常(RuntimeException)和非运行时异常,这两种异常有很大的区别,也称之为不检查异常(Unchecked Exception)和检查异常(Checked Exception)。

Error与Exception:

  • Error一般是指与虚拟机相关的问题,如系统崩溃、虚拟机错误、动态链接失败等,这种错误无法恢复或不可能捕获,将导致应用程序中断,这些错误是不可查的,因为它们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况,通常应用程序不应该试图使用catch块来捕获Error对象,在定义该方法时,也无须在其throws子句中声明该方法可能抛出Error及其任何子类。
  • Exception是程序本身可以处理的异常,这种异常分两大类,运行时异常和非运行时异常,程序中应当尽可能去处理这些异常。 

 

运行时异常和非运行时异常:

  • 运行时异常都是RuntimeException类及其子类异常,如NullPointerExceptionIndexOutOfBoundsException等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理,这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。
  • 非运行时异常是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过,如IOExceptionSQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。

3、如何处理异常

Java的异常处理本质上是抛出异常和捕获异常,其机制主要依赖于try、catch、finally、throw和throws五个关键字。
try:字面意思是尝试,试验,就是试着运行可能出现异常的代码
catch:捕捉,抓住的意思,用于捕获在try语句块中发生的异常
finally:最后,finally语句块总是会被执行,它主要用于回收在try块里打开的资源,如数据库连接、网络连接与磁盘文件等,只有finally块,执行完成之后,才会回来执行try或者catch块中的return或者throw语句,如果finally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止
throw:该关键字是用于方法体内部,用来抛出一个Throwable类型的异常。如果抛出了检查异常, 则还应该在方法头部声明方法可能抛出的异常类型。该方法的调用者也必须检查处理抛出的异常
throws:用在方法签名中,用于声明该方法可能抛出的异常。仅当抛出了检查异常,该方法的调用者才必须处理或者重新抛出该异常

异常处理的语法
try...catch...比较适用于有异常要处理,但是没有资源要释放的。

try {
    // 可能发生异常的代码
} catch (Exception e) {
    // 处理异常的代码
}

try...catch...finally...比较适用于既有异常要处理又要释放资源的代码。

try {
    // 可能发生异常的代码
} catch (Exception e) {
    // 处理异常的代码
} finally {
    // 释放资源的代码;
}

try...finally...比较适用于内部抛出的是运行时异常,并且有资源要被释放。

try {
    // 可能发生异常的代码
} finally {
    // 释放资源的代码
}

将开始的代码用try...catch...改写下

public class ExceptionDemo {
    public static void main(String[] args) {
        try {
            int x = 6 / 0;
            System.out.println("x = " + x);
        } catch (Exception e) {
            System.out.println(e.getMessage());
            System.out.println(e.toString());
            e.printStackTrace();
        }

        System.out.println("程序正常结束。");
    }
}

运行结果

/ by zero
java.lang.ArithmeticException: / by zero
java.lang.ArithmeticException: / by zero
    at ExceptionDemo.main(ExceptionDemo.java:4)
程序正常结束。

可以看到即使try语句块中出现了异常,在catch中处理了异常之后,后面的语句正常执行,接下来再看看加上finally的情况

public class ExceptionDemo {
    public static void main(String[] args) {
        try {
            int x = 6 / 0;
            System.out.println("x = " + x);
        } catch (Exception e) {
            System.out.println(e.getMessage());
            System.out.println(e.toString());
            e.printStackTrace();
        } finally {
            System.out.println("一定会执行的代码。");
        }

        System.out.println("程序正常结束。");
    }
}

运行结果

/ by zero
java.lang.ArithmeticException: / by zero
java.lang.ArithmeticException: / by zero
    at ExceptionDemo.main(ExceptionDemo.java:4)
一定会执行的代码。
程序正常结束。

当catch语句块中处理完异常后,接着会执行finally中的代码,在finally中的代码一定会被执行(通常用于资源的释放操作),即使catch中有return语句

public class ExceptionDemo {
    public static void main(String[] args) {
        try {
            int x = 6 / 0;
            System.out.println("x = " + x);
        } catch (Exception e) {
            System.out.println(e.getMessage());
            System.out.println(e.toString());
            e.printStackTrace();
            return;
        } finally {
            System.out.println("一定会执行的代码。");
        }

        System.out.println("程序正常结束。");
    }
}

运行结果

/ by zero
java.lang.ArithmeticException: / by zero
java.lang.ArithmeticException: / by zero
    at ExceptionDemo.main(ExceptionDemo.java:4)
一定会执行的代码。

当然在catch中return之后,程序就结束了,所以"程序正常结束。"这句话也就没有输出了。

很多情况下,一条语句或者单个的代码段可能引起多个异常。处理这种情况,我们需要定义两个或者更多的catch子句,每个子句捕获一种类型的异常,当异常被引发时,每个catch子句被依次检查,第一个匹配异常类型的子句执行,当一个catch子句执行以后,其他的子句将被忽略。编写多重catch语句块时应注意顺序问题:先小后大,即先子类后父类,否则更具体的子类异常将被屏蔽。

finally子句是可选项,可以有也可以无,但是每个try语句至少需要一个catch或者finally子句。

现在,我们学习了如何捕获并处理程序中发生的异常,不过有时候我们并不知道如何处理发生的异常,这是就可以用java提供的throw关键字,Throw的语法形式如下

throw ThrowableInstance;

这里的ThrowableInstance一定是Throwable类类型或者Throwable子类类型的一个对象。简单的数据类型,例如int,char以及非Throwable类,例如String或Object,不能用作异常。有两种方法可以获取Throwable对象:在catch子句中使用参数或者使用new操作符创建。

程序执行完throw语句之后立即停止;throw后面的任何语句不被执行,最邻近的try块用来检查它是否含有一个与异常类型匹配的catch语句,如果发现了匹配的块,控制转向该语句;如果没有发现,次包围的try块来检查,以此类推。如果没有发现匹配的catch块,默认异常处理程序中断程序的执行并且打印堆栈轨迹。

public class TestThrow {
    public static void main(String[] args) {
        int x = div(6, 0);
        System.out.println("x = " + x);
    }

    public static int div(int a, int b) {
        if (b == 0) {
            throw new ArithmeticException("除数不能为0。");
        }

        return a / b;
    }
}

这里直接在div方法中用throw关键字抛出异常,如果一个方法可以导致一个异常但不处理它,我们可以在方法声明中包含一个throws子句。一个throws子句列举了一个方法可能引发的所有异常类型。这对于除了Error或RuntimeException及它们子类以外类型的所有异常是必要的。一个方法可以引发的所有其他类型的异常必须在throws子句中声明,否则会导致编译错误,下面是throws子句的方法声明的通用形式:

public static int div(int a, int b) throws ArithmeticException{
    // code
}

ArithmeticException是该方法可能引发的的异常,这里也可以是异常列表,中间以逗号隔开。

public static int div(int a, int b) throws ArithmeticException, NullPointerException{
    // code
}

当然也可以直接抛出Exception,表示该方法可能引发的的所有异常,不过不建议这么做,因为当我们代码逻辑很复杂时,如果没有看代码,并不知道是哪里的代码引起的异常

public static int div(int a, int b) throws Exception{
    // code
}

将上面的例子用throws关键字

public class TestThrows {
    public static void main(String[] args) {
        try {
            int x = div(6, 0);
            System.out.println("x = " + x);
        } catch (ArithmeticException e) {
            System.out.println("发生了算数异常。");
        }
    }

    public static int div(int a, int b) throws ArithmeticException {
        return a / b;
    }
}

Throws抛出异常的规则

  • 如果发生的异常是是不受检查异常(unchecked exception),即Error、RuntimeException或它们的子类,那么可以不使用throws关键字来声明要抛出的异常,编译仍能顺利通过,但在运行时会被系统抛出
  • 必须声明方法可抛出的任何检查异常(checked exception),即如果一个方法可能出现受可查异常,要么用try-catch语句捕获,要么用throws子句声明将它抛出,否则会导致编译错误
  • 仅当抛出了异常,该方法的调用者才必须处理或者重新抛出该异常,当方法的调用者无力处理该异常的时候,应该继续抛出给调用者,而不应该简单的打印或者干脆不处理
  • 调用方法必须遵循任何可查异常的处理和声明规则,若覆盖一个方法,则不能声明与覆盖方法不同的异常,声明的任何异常必须是被覆盖方法所声明异常的同类或子类

4、自定义异常

Java内置的异常类可以描述在编程时出现的大部分异常情况,不过有时候在实际项目中需要结合业务抛出跟接地气的异常,只需继承Exception类来自定义我们需要的异常即可

public class SelfDefinitionException {
    public static void main(String[] args) {
        try {
            compare(-1);
        } catch (MyException e) {
            System.out.println(e);
        }
    }

    static void compare(int a) throws MyException {
        if (a < 0) {
            throw new MyException("输入的参数不能小于0。");
        }
    }
}

class MyException extends Exception {
    public MyException(String message) {
        super(message);
    }
}

5、异常总结

  • 异常是程序运行过程过程出现的错误,在Java中用类来描述,用对象来表示具体的异常,Java将其区分为Error与Exception,Error是程序无力处理的错误,Exception是程序可以处理的错误,异常处理是为了程序的健壮性
  • Java异常类来自于Java API定义和用户扩展,通过继承Java API异常类可以实现异常的转译
  • 异常能处理就处理,不能处理就抛出,最终没有处理的异常JVM会进行处理

 

posted @ 2016-12-23 23:02  不能差不多  阅读(669)  评论(0编辑  收藏  举报