浅析java异常处理机制—中级篇

来源:互联网 发布:linux查看snmp团体名 编辑:程序博客网 时间:2024/05/21 18:42

上一篇我们说到异常处理机制有:捕获异常和抛出异常。下面我们就详细介绍一下。


捕获异常

代码中捕获异常使用:try、catch 和 finally

处理流程:

这里写图片描述
1)当try没有捕获到异常时:try语句块中的语句逐一被执行,程序将跳过catch语句块,执行finally语句块和其后的语句;
2)当try捕获到异常,catch语句块里没有处理此异常的情况:当try语句块里的某条语句出现异常时,而没有处理此异常的catch语句块时,此异常将会抛给JVM处理,finally语句块里的语句还是会被执行,但finally语句块后的语句不会被执行;
3)当try捕获到异常,catch语句块里有处理此异常的情况:在try语句块中是按照顺序来执行的,当执行到某一条语句出现异常时,程序将跳到catch语句块,并与catch语句块逐一匹配,找到与之对应的处理程序,其他的catch语句块将不会被执行,而try语句块中,出现异常之后的语句也不会被执行,catch语句块执行完后,执行finally语句块里的语句,最后执行finally语句块后的语句;
4)如果一个方法调用了另外一个声明抛出异常的方法,那么这个方法要么处理异常,要么声明抛出。

语法规则:

这里写图片描述
1) 必须在 try 之后添加 catch 或 finally 块。try 块后可同时接 catch 和 finally 块,但至少有一个块。
2) 必须遵循块顺序:若代码同时使用 catch 和 finally 块,则必须将 catch 块放在 try 块之后。
3) catch 块与相应的异常类的类型相关。
4) 一个 try 块可能有多个 catch 块。若如此,则执行第一个匹配块。即Java虚拟机会把实际抛出的异常对象依次和各个catch代码块声明的异常类型匹配,如果异常对象为某个异常类型或其子类的实例,就执行这个catch代码块,不会再执行其他的 catch代码块
5) 可嵌套 try-catch-finally 结构。
6) 在 try-catch-finally 结构中,可重新抛出异常。
7) 除了下列情况,总将执行 finally 做为结束:JVM 过早终止(调用 System.exit(int));在 finally 块中抛出一个未处理的异常;计算机断电、失火、或遭遇病毒攻击。

抛出异常

在声明方法时候抛出异常

语法:throws(略)
为什么要在声明方法抛出异常?
方法是否抛出异常与方法返回值的类型一样重要。假设方法抛出异常却没有声明该方法将抛出异常,那么客户程序员可以调用这个方法而且不用编写处理异常的代码。那么,一旦出现异常,那么这个异常就没有合适的异常控制器来解决。
为什么抛出的异常一定是已检查异常?
RuntimeException与Error可以在任何代码中产生,它们不需要由程序员显示的抛出,一旦出现错误,那么相应的异常会被自动抛出。遇到Error,程序员一般是无能为力的;遇到RuntimeException,那么一定是程序存在逻辑错误,要对程序进行修改;只有已检查异常才是程序员所关心的,程序应该且仅应该抛出或处理已检查异常。而已检查异常是由程序员抛出的,这分为两种情况:客户程序员调用会抛出异常的库函数;客户程序员自己使用throw语句抛出异常。
注意:
覆盖父类某方法的子类方法不能抛出比父类方法更多的异常,所以,有时设计父类的方法时会声明抛出异常,但实际的实现方法的代码却并不抛出异常,这样做的目的就是为了方便子类方法覆盖父类方法时可以抛出异常。

在方法中抛出异常

语法:throw(略)
抛出什么异常?
对于一个异常对象,真正有用的信息是异常的对象类型,而异常对象本身毫无意义。比如一个异常对象的类型是ClassCastException,那么这个类名就是唯一有用的信息。所以,在选择抛出什么异常时,最关键的就是选择异常的类名能够明确说明异常情况的类。
异常对象通常有两种构造函数:一种是无参数的构造函数;另一种是带一个字符串的构造函数,这个字符串将作为这个异常对象除了类型名以外的额外说明。

throw和throws的区别

public class TestThrow{    public static void main(String[] args)    {        try        {            //调用带throws声明的方法,必须显式捕获该异常            //否则,必须在main方法中再次声明抛出            throwChecked(-3);                    }        catch (Exception e)        {            System.out.println(e.getMessage());        }        //调用抛出Runtime异常的方法既可以显式捕获该异常,        //也可不理会该异常        throwRuntime(3);    }    public static void throwChecked(int a)throws Exception    {        if (a > 0)        {            //自行抛出Exception异常            //该代码必须处于try块里,或处于带throws声明的方法中            throw new Exception("a的值大于0,不符合要求");        }    }    public static void throwRuntime(int a)    {        if (a > 0)        {            //自行抛出RuntimeException异常,既可以显式捕获该异常            //也可完全不理会该异常,把该异常交给该方法调用者处理            throw new RuntimeException("a的值大于0,不符合要求");        }    }}

补充:throwChecked函数的另外一种写法如下所示:

public static void throwChecked(int a)    {        if (a > 0)        {            //自行抛出Exception异常            //该代码必须处于try块里,或处于带throws声明的方法中            try            {                throw new Exception("a的值大于0,不符合要求");            }            catch (Exception e)            {                e.printStackTrace();            }        }    }

注意:此时在main函数里面throwChecked就不用try异常了。

在声明方法抛出异常还是在方法中捕获异常?

处理原则:捕捉并处理哪些知道如何处理的异常,而传递哪些不知道如何处理的异常。
注意:如果每个方法都是简单的抛出异常,那么在方法调用方法的多层嵌套调用中,Java虚拟机会从出现异常的方法代码块中往回找,直到找到处理该异常的代码块为止。然后将异常交给相应的catch语句处理。如果Java虚拟机追溯到方法调用栈最底部main()方法时,如果仍然没有找到处理异常的代码块,将按照下面的步骤处理:
第一、调用异常的对象的printStackTrace()方法,打印方法调用栈的异常信息。
第二、如果出现异常的线程为主线程,则整个程序运行终止;如果非主线程,则终止该线程,其他线程继续运行。
通过分析思考可以看出,越早处理异常消耗的资源和时间越小,产生影响的范围也越小。因此,不要把自己能处理的异常也抛给调用者。

使用finally块释放资源

finally关键字保证无论程序使用任何方式离开try块,finally中的语句都会被执行。在以下三种情况下会进入finally块:
(1) try块中的代码正常执行完毕。
(2) 在try块中抛出异常。
(3) 在try块中执行return、break、continue。
因此,当你需要一个地方来执行在任何情况下都必须执行的代码时,就可以将这些代码放入finally块中。当你的程序中使用了外界资源,如数据库连接,文件等,必须将释放这些资源的代码写入finally块中。
必须注意的是:在finally块中不能抛出异常。JAVA异常处理机制保证无论在任何情况下必须先执行finally块然后再离开try块,因此在try块中发生异常的时候,JAVA虚拟机先转到finally块执行finally块中的代码,finally块执行完毕后,再向外抛出异常。如果在finally块中抛出异常,try块捕捉的异常就不能抛出,外部捕捉到的异常就是finally块中的异常信息,而try块中发生的真正的异常堆栈信息则丢失了。
请看下面的代码:

Connection  con = null;try{    con = dataSource.getConnection();    ……}catch(SQLException e){    ……    throw e;//进行一些处理后再将数据库异常抛出给调用者处理}finally{    try    {        con.close();    }    catch(SQLException e){    e.printStackTrace();    ……}}

运行程序后,调用者得到的信息如下
java.lang.NullPointerException
at myPackage.MyClass.method1(methodl.java:266)
而不是我们期望得到的数据库异常。这是因为这里的con是null的关系,在finally语句中抛出了NullPointerException,在finally块中增加对con是否为null的判断可以避免产生这种情况。

总结

我们编写程序时除了抛出异常信息外,可以多打印一些日志,这样当程序出现错误时可以快速定位到异常出现的位置。方便我们快速排错。无论方法执行成功还是失败都应该打印日志出来。这样我们可以通过日志快速定位到异常的位置,在通过异常信息判断出异常的类型。当我们也可以创建自己的异常类,处理一些java内置异常不能明确说明的异常情况,但是这种情况比较少见,而且唯一有用的只是异常的类型名这个信息。

1 0
原创粉丝点击