在Java程序语言设计中,所有的异常都是由类来表示,异常对象都是派生于Throwable类的一个实例。下面是Java异常层析结构的一个简单示意图:
由图可知,所有异常类都直接或间接的继承于Throwable类。
具体分类:
Java把所有非正常情况分为Error(错误) 和 Exception(异常)
Error
Error类层次结构描述了Java运行时系统的内部错误和资源耗尽错误,应用程序不应该抛出此类型的对象。这种情况一般很少出现。
- VirtualMachineError(虚拟机错误)
- ThreadDeath(线程锁死)
Exception
Exception类层次结构又分为两个分支 RuntimeException(运行时异常)和其他异常(代表:IOException )。
派生于RuntimeException的常见异常的有以下几个
- ClassCastException(类型转换异常)
- IndexOutOfBoundsException(下标越界异常)
- NullPointerException(空指针异常)
若是出现RuntimeException异常,那么一就定是你的问题,这是一条相当由道理的规则。
不派生于RuntimeException的异常有
- IOException(I /O流异常)
- 试图在文件尾部后面读取数据
- 试图打开一个不存在的文件
- 试图根据指定的字符串查找Class对象,而这个字符串表示的对象不存在。
Java语言规范将派生于Error类 或RuntimeException类的所有异常成为非受察(unchecked)异常,所有其他异常称为受察(checked)异常。
异常处理机制
当程序出现异常时无非是将可能出现的异常抛出去不予理睬或者捕获可能出现的异常并加以处理。
抛出:
抛出异常依赖于throw和throws关键字
throw:抛出一个具体的异常对象,可以单独作为语句使用,可以在符合某种条件下抛出异常。
throws:用在方法签名(方法名和参数列表统称为方法签名)中,声明该方法可能抛出的异常(类)。
例1:
/**
* @author 北冥有熊
* 2018年11月3日
*/
public class ecxeptionTest {
public static void main(String[] args) {
try {
mathException(10,0);
}
catch (Exception e) {
System.out.println(e.getMessage()); //返回throwable详细信息字符串
e.printStackTrace(); //将throwable对象的堆栈跟踪输出至错误输出流。
}
}
public static void mathException(int a,int b) throws Exception{ //声明有可能抛出一个异常
if (b==0) {
//在此方法里面并没有对可能出现的异常进行处理,而是产生一个具体的异常对象并抛出给调用者
throw new Exception("除数不能为0");
} else {
int num = a/b;
System.out.println(a+"/"+b+"=:"+num);
}
}
}
其中,声明异常部分也可以声明多个异常,之间用 “,”隔开
public static void mathException(int a,int b) throws Exception,OtherException{}
捕获并处理:
捕获处理异常依赖于try、catch、finally等关键字
try:存放需要捕获指定异常的语句块。
catch:捕获异常对象,并加以处理,chtch语句可以定义多个,针对不同异常如果有不同的处理手段。
finally:在其里面的代码块必须执行
/**
* @author 北冥有熊
* 2018年11月4日
*/
public class exceptionTest01 {
public static void main(String[] args) {
try {
int a = 10;
int num = a/0; //发现异常,停止运行,转向catch语句块
System.out.println("-------1-------"); //不执行
} catch (Exception e) {
System.out.println("-------2-------"); //执行
}finally {
System.out.println("-------3-------"); //必须执行
}
}
}
结果是:
-------2-------
-------3-------
值得注意的是,当try、catch语句块中有return时有如下特点:
/**
* @author 北冥有熊
* 2018年11月4日
*/
public class Test03 {
public static void main(String[] args) {
System.out.println(new Demo().getNum1());
System.out.println(new Demo().getNum2());
}
}
class Demo{
//finally中没有return
String getNum1(){
String t="";
try{
t="try";
return t; //此处返回的t并非上一句中的t,而是系统重新指定局部引用的t'
//只不过t'指向了引用t对应的值,也就是try
}catch(Exception e){
t="catch"; //不执行
return t;
}finally{
t="finally";
System.out.println("===="+t); // 输出t=finally,此处修改的是t而非t',
//t'虽然指向t但返回的是未修改的t'-----指向----->t="try"
}
}
//fianlly中有return
String getNum2(){
String t="";
try{
t="try";
return t; //方法结束,去检查finally语句块中有没有return语句
}catch(Exception e){
t="catch"; //不执行
}finally{
t="finally";
System.out.println("===="+t); // 输出t=finally
return t; // 返回修改后的t'指向上一句被修改过得t,并且返回[finally中一般不写return
//此处警告,但能运行
}
}
}
getNum1方法结果:【finally中没有return】
====finally //t'-----指向----->t="try"被修改为finally
try //在未修改t之前已经返回
getNum2方法结果:【finally中有return】
====finally //t被修改为finally
finally //修改之后并覆盖前面的return返回值
对于上面代码的注解大家可能很疑惑,下面看用javap -verbose Test03来显示目标文件(.class文件)字节码信息:
这里我们只看有效方法主体;
public static java.lang.String main();
descriptor: ()Ljava/lang/String;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=4, args_size=0
0: ldc #5 // String
2: astore_0
3: ldc #6 // String try
5: astore_0
6: aload_0
7: astore_1
8: ldc #7 // String finally
10: astore_0
11: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
14: new #8 // class java/lang/StringBuilder
17: dup
18: invokespecial #9 // Method java/lang/StringBuilder."<init>":()V
21: ldc #10 // String ====
23: invokevirtual #11 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
26: aload_0
27: invokevirtual #11 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
30: invokevirtual #12 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
33: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
36: aload_1
37: areturn
38: astore_1
...
88: ldc #10 // String ====
90: invokevirtual #11 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
93: aload_0
94: invokevirtual #11 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
97: invokevirtual #12 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
100: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
103: aload_3
104: athrow
Exception table:
from to target type
3 8 38 Class java/lang/Exception
3 8 74 any
38 44 74 any
LocalVariableTable:
Start Length Slot Name Signature
39 35 1 e Ljava/lang/Exception;
3 102 0 t Ljava/lang/String;
}
首先看LocalVariableTable信息,这里面定义了两个变量 一个是e Exception 类型,一个是t String类型
接下来看方法主体Code部分:
- 第[0-2]行:从常量池入口#5取出内容推到栈顶,将"null"引用传递给给第0个变量,也就是String t = "";
- 第[3-6]行:从常量池入口#6取出内容推到栈顶,也就是执行try语句块,将"try"引用传递给第0个变量。
- 第[7]行:重点是第7行,将"try"付给第1个变量(astore_1),但是这里面第1个变量并没有定义,(纳闷中...?)
- 第[8-10] 行:对第0个变量进行赋值操作,也就是t="finally"。
- 第[36-37]行:把第1个变量对应的值返回
通过字节码,我们发现,在try语句的return块中,return 返回的引用变量(t 是引用类型)并不是try语句外定义的引用变量t,而是系统重新定义了一个局部引用t’,这个引用指向了引用t对应的值,也就是try ,即使在finally语句中把引用t指向了值finally,因为return的返回引用已经不是t ,所以引用t的对应的值和try语句中的返回值无关了。
总结:
- finally语句块中的代码是必须执行的,当在try语句块总中出现异常,代码停止当前运行,转向catch语句块,再转向finally语句块。
- 在try、catch、finally中,若try、catch中有return时,且执行到return时,首先会检查finally语句块中有没有return,若finally中没有就返回try、catch语句块中的return返回值;若finally语句块中有return,则finally中的return值会覆盖try、catch中的return返回值。
- 若try、catch中有异常信息时,finally块中应避免使用return语句,因为finally块中如果使用return语句,会显示的屏蔽掉try、catch块中的异常信息,这并不是我们想要的。