java异常及其处理机制

来源:互联网 发布:diy图片制作软件 编辑:程序博客网 时间:2024/06/07 00:32

Java异常处理机制中finally的问题

1、什么叫做异常

所谓异常是指程序的错误,即指系统错误,也包括逻辑错误,在语义层面上异常是程序的“意外、例外”。程序 = 数据 + 算法 + 代码,那么异常则是代码进行算法实现时出现数据或系统的意外、例外,从而导致了算法没有预测到的错误。

2、为什么要进行异常处理

有人会说,一个好的算法可以避免异常、错误的出现,也就不需要异常的处理情况。基于这种思想考虑的人,往往忽略了人的因素,因为算法本身是基于数据进行计算,而数据又是一个特定的集合,如果我们能保证程序中的数据都在这个特定集合内进行操作,那么程序就没有异常?答案是肯定有异常,因为程序是在计算机中进行执行,而机器又具有本身的特性,程序的数据虽然进行了控制,但是程序执行的顺序、条件和结果可能又会有所不同,这种不同就可能是系统错误。此外,程序中的代码是人进行编写,在实现算法的过程中,人的逻辑也可能出错,这种错误称为逻辑错误。

3、异常种类

在java中将异常分为两个大类:错误Error、异常Exception。此外,所有的异常、错误的顶层基类为Throwable类。
Error类:Java中的内部错误、资源耗尽错误,这类错误程序处理不了。
Exception类:Checked Exception类、 UnChecked Exception类。UnChecked Exception类的错误是Java程序编码中逻辑错误,它的基类是RuntimeException类,这是java程序员可以修正或者避免的错误,也叫运行时异常(但不要理解为,JVM运行时抛出的异常,两者具有包含关系),如IIlegalArgumentException、NullPointerException和IndexOutOfBoundsException等常见异常。checked Exception是由程序和运行环境进行数据交换、动作执行所造成的错误,如IOException、EOFException和SQLException、DataFormatException等。

RuntimeException在java代码中并不要求必须catch,此外的Exception类都必须进行catch


4、异常、错误的层次关系

异常层次关系

5、JVM正常调用方法
import java.io.FileInputStream;    import java.io.FileNotFoundException;    import java.io.IOException;    public class Test {        public static void main(String[] args) {            String str = test();            System.out.println(str);        }        public static String test() {            System.out.println("test invoked");            return "test"; //返回        }    }

通过javap -verbose指令打印出编译后的指令集合为
public static java.lang.String test();
Code:
Stack=2, Locals=0, Args_size=0
0: getstatic #3; //Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #5; //String test invoked
5: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: ldc #6; //String test
10: areturn
LineNumberTable:
line 13: 0
line 14: 8
其中指令getstatic、ldc、invokevirtual和areturn都是比较常见的指令,它们分别表示获取静态字段、加载常量、调用虚方法和返回reference值。现代编译器都十分的只能,可以看到指令的后面都加了注释,而#3、#5和#6则是引用class file中的常量池Constanc_pool中的常量。 从上面可以看出,test()中的打印和返回语句使用了五条指令。这里我用的是JDK1.5的版本。


6、JVM调用方法中存在try…finally语句快时
public class Test1 {        public static void main(String[] args) {            String str = test();            System.out.println(str);        }        public static String test() {            try {                System.out.println("try block");                return "try"; //返回            } finally {                System.out.println("finally block");            }        }    }

这里我们将代码进行try…finally块进行包装,没有异常捕获,十分简单的一个操作。下面看编译器给出的指令集合。

public static java.lang.String test();
Code:
Stack=2, Locals=2, Args_size=0
0: getstatic #3; //Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #5; //String try block
5: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: ldc #6; //String try
10: astore_0
11: getstatic #3; //Field java/lang/System.out:Ljava/io/PrintStream;
14: ldc #7; //String finally block
16: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
19: aload_0
20: areturn
21: astore_1
22: getstatic #3; //Field java/lang/System.out:Ljava/io/PrintStream;
25: ldc #7; //String finally block
27: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
30: aload_1
31: athrow Exception table:
from to target type
0 11 21 any
21 22 21 any
LineNumberTable:
line 10: 0
line 11: 8
line 13: 11

从上面的指令集中可以看出,简单的try…finally块的引入对于编译器来说,需要指令集合的大小明显不同。在指令集合中0、3、5、8可以看出是try块中的打印语句,和上节的test()的指令相同,但在areturn指令中,又添加了10、11、14、16这四条指令,从注释中可以看出是finally块的打印语句。也就是说,在执行完打印“try block”后不是立即return “try”,而是将控制权转移到了finally块进行执行,然后才返回到return进行返回。但是,这里可以看到,在areturn指令后面还有21、22、25、27、30、31这六条指令,这可能会给初学者造成困惑,都return了怎么还有后面的语句?但对于学过JVM虚拟机或者看过Java Specifications文档的人都知道,Code属性中有个Exception表,也就是Exception table里面存放这Code指令集合中的异常类型对照表,虽然我们使用try…finally块没有显示的进行catch异常,但是Exception table中仍然存在对异常的类型匹配向,这里type的值为any也就是任何类型。其中的from、to、target则说明从try块的执行中任何一个地方出现异常,都会跳转到指令集合中索引为21的地方进行执行。通俗来讲,就是我们学编程时提过的无论如何都会执行finally块中的代码,这里就是指令实现。

那么,当finally块中存在return语句时,是执行try中的return还是finally中的return?

    public class Test2 {        public static void main(String[] args) {            String str = test();            System.out.println(str); //打印出try还是finally ?        }        public static String test() {            try {                System.out.println("try block");                return "try"; //返回            } finally {                System.out.println("finally block");                return "finally"; //返回            }        }    }

答案很简单,但是在编译后的指令中是如何实现的呢?

public static java.lang.String test();
Code:
Stack=2, Locals=2, Args_size=0
0: getstatic #3; //Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #5; //String try block
5: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: ldc #6; //String try
10: astore_0
11: getstatic #3; //Field java/lang/System.out:Ljava/io/PrintStream;
14: ldc #7; //String finally block
16: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
19: ldc #8; //String finally
21: areturn
22: astore_1
23: getstatic #3; //Field java/lang/System.out:Ljava/io/PrintStream;
26: ldc #7; //String finally block
28: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
31: ldc #8; //String finally
33: areturn Exception table:
from to target type
0 11 22 any
22 23 22 any
LineNumberTable:
line 10: 0
line 11: 8
line 13: 11
line 14: 19
line 13: 22
line 14: 31

这里对比前面的指令集合,稍微有些不同。在第19个指令中,前面是aload_0,后面是ldc #8。在最后两个指令中,前面是aload_1、athrow,后面是ldc #8、areturn。这里load、ldc指令是关键,因为他们加载数据的地方不同,load是从局部变量表中进行加载,而ldc是从常量池中进行加载数据到操作数栈,在areturn返回数据时,从操作数栈顶弹出的元素值不同。

其实,在java代码中try…finally进行编译时,编译器会进行优化。编译器不仅仅是将java代码进行简单的翻译,更多的是从代码的资源利用效率和代码翻译角度进行合理的组织。


7、JVM调用方法中存在try…catch…finally
    import java.io.FileInputStream;    import java.io.FileNotFoundException;    public class Test3 {        public static void main(String[] args) {            String str = test();            System.out.println(str);        }        public static String test() {            try {                System.out.println("try block");                FileInputStream is = new FileInputStream("aa.txt");                return "try";            } catch(FileNotFoundException e) {                System.out.println("catch block");                return "catch";            } finally {                System.out.println("finally block");            }        }    }

这段程序的执行结果,相信有些人可能会很迷惑。但有编程经验的人应该可以得出正确的答案。先执行打印try中代码,在FileNotFoundException中则执行打印catch block,在catch块中的return前到finally块中的打印子句。最后才将catch字符串返回。

try blockcatch blockfinally blockcatch

下面来看看指令集合:

public static java.lang.String test();
Code:
Stack=3, Locals=3, Args_size=0
0: getstatic #3; //Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #5; //String try block
5: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: new #6; //class java/io/FileInputStream
11: dup
12: ldc #7; //String aa.txt
14: invokespecial #8; //Method java/io/FileInputStream.””:(Ljava/lang/String;)V
17: astore_0
18: ldc #9; //String try
20: astore_1
21: getstatic #3; //Field java/lang/System.out:Ljava/io/PrintStream;
24: ldc #10; //String finally block
26: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
29: aload_1
30: areturn
31: astore_0
32: getstatic #3; //Field java/lang/System.out:Ljava/io/PrintStream;
35: ldc #12; //String catch block
37: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
40: ldc #13; //String catch
42: astore_1
43: getstatic #3; //Field java/lang/System.out:Ljava/io/PrintStream;
46: ldc #10; //String finally block
48: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
51: aload_1
52: areturn
53: astore_2
54: getstatic #3; //Field java/lang/System.out:Ljava/io/PrintStream;
57: ldc #10; //String finally block
59: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
62: aload_2
63: athrow
Exception table:
from to target type
0 21 31 Class java/io/FileNotFoundException
0 21 53 any
31 43 53 any
53 54 53 any
将指令集合进行分解:

第一部分是正常执行流程,也就是上面try…finally
0: getstatic #3; //Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #5; //String try block
5: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: new #6; //class java/io/FileInputStream
11: dup
12: ldc #7; //String aa.txt
14: invokespecial #8; //Method java/io/FileInputStream.””:(Ljava/lang/String;)V
17: astore_0
18: ldc #9; //String try
20: astore_1
21: getstatic #3; //Field java/lang/System.out:Ljava/io/PrintStream;
24: ldc #10; //String finally block
26: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
29: aload_1
30: areturn
第二部分是catch到FileNotFoundException异常
31: astore_0
32: getstatic #3; //Field java/lang/System.out:Ljava/io/PrintStream;
35: ldc #12; //String catch block
37: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
40: ldc #13; //String catch
42: astore_1
43: getstatic #3; //Field java/lang/System.out:Ljava/io/PrintStream;
46: ldc #10; //String finally block
48: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
51: aload_1
52: areturn
第三部分是catch到any异常时执行finally子句
53: astore_2
54: getstatic #3; //Field java/lang/System.out:Ljava/io/PrintStream;
57: ldc #10; //String finally block
59: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
62: aload_2
63: athrow
上面程序执行我们可以进行如下分析:a、从try块开始执行,如果没有抛出任何异常,则从0~30的执行正常执行完成,方法返回完成,对应第一部分的子指令集合;b、当try中抛出异常,catch捕获到该异常则控制权跳转到第二部分进行执行,同样正常执行完成,方法返回完成,退出。c、当catch没有捕获到该异常,则控制权直接跳转到finally中进行执行,并返回,对应第三部分。


总结

在java程序中,try…finally、try…catch和try…catch…finally中语句的执行,只要牢记try中抛出异常则跳转到相应catch块中进行执行,而在catch中将要return或throw时,先要跳转到finally中进行执行。如果try中有return或throw,则在执行前,先要跳转到finally中进行执行。表现在编译指令中,我们从上面可以看到,finally块的指令重复出现了多次,这也就是为什么finally块一定会执行。

0 0
原创粉丝点击