Try就像一个警报器,任何进入他领域的陌生人,这个警报器就会发出警报声,然后把陌生人交给catch处理。
在try语句中发生了任何异常,try语句块就会把控制权转移给catch语句,如果这个异常与catch语句相匹配,那么控制流程就进入这个catch中,否则,匹配下一个catch语句。
在一个方法中可以有多个try语句,虽然很少这样做,同时,一个try语句也可以有多个catch语句。
请看如下代码:
package MyPackage;
public class ExceptionTest {
public static void main(String[] args) {
int i=0;
int j =1;
try{
System.out.println(j/i);
}catch(ArithmeticException ex){
}catch(Exception ex){
}
}
}
在这段代码中i/j会抛出一个ArithmeticException异常,然后,控制权转移到第一个catch语句,catch语句可能会进行一些异常处理。
现将这个代码反汇编成字节码,如下:
先来解释一下这个main方法的字节码:偏移量0到3的字节码(iconst_0 to istore_2)对于源代码中的如下两行语句,主要完成初始化操作。
int i=0;
int j =1;
偏移量4到13的字节码(getstatic 到第一个goto)对于源代码的try语句块,如下:
try{
System.out.println(j/i);
}
接下来的偏移量为16到17的字节码 ( astore_3到goto)对应于源代码中的第一个catch语句。
最后两行偏移量为20到21的字节码(astore_3 到 return)对应于源代码中的第二个catch语句。
接下来看到的是一个Exception table(异常表)。这个表由两行组成,分别对应于try语句的两个catch;java虚拟机为会每一个catch,在Exception table中创建一个条目。
在这段字节码中,idiv指令完成了核心功能,除法操作。这个指令在除数为零时会抛出一个ArithmeticException异常。如果idiv指令抛出了一个ArithmeticException异常,那么java虚拟机就会去Exception table中查找这个catch语句(通过比较第四列type中的字符串),找到了这个catch之后,还要看一看from和to列,如果idiv指令所在的偏移量在这个form 和to值之间,就匹配成功,然后java虚拟机会跳到traget值对于的字节码;
如果java虚拟机在Exception table中没有找到匹配的异常,java虚拟机就会弹出当前栈帧,并将这个异常压入调用这个方法的栈帧中,然后重新抛出异常,执行刚才所说的操作。
现在解释一下Exception table,如果一个方法有try…catch语句,那么这个方法就有会一个Exception table,java虚拟机会为每一个catch,在Exception table中创建条目。每一个条目由四列组成,每一列的含义如下:
from:try语句所保护的字节码的起始偏移量。
to:try语句所保护的字节码的终点。
Target:要跳转到的catch语句(偏移量)
Type:catch类型
from …to标示了try语句块的保护范围,如果在这个范围内抛出了一个type类型的异常,就会跳转到target所标示的catch语句块中。
现在,再描述一下整个过程,当java虚拟机执行到偏移量为9的idiv指令时,抛出了一个ArithmeticException异常。然后java虚拟开始查找这个方法的Exception table,通过比较type列,java虚拟机找到了一个条目,接下来java虚拟机会用刚才抛出异常的idiv指令的偏移量,来看看这个偏移量是否在form…to之间,如果在,就匹配,跳转到target所标示的catch中。
在java中可以throw显示抛出一个异常,同字节码中也有对于的指令,这个指令是athrow指令。
一个方法主要通过两种方式返回,一种是正常返回,一种是异常返回。
正常返回就是那些通过return方式返回的,当然了,如果返回类型是void,可能不必在源代码中显示使用return。
异常返回就是方法中的某个指令抛出了一个异常,从而迫使这个方法返回。 如果一个方法抛出了一个异常,java虚拟机会在Exception table中查找相应的异常,如果找到了,则按照Exception table中所指示的方式进行处理。否则,java虚拟机会弹出当前方法的栈帧,然后将这个异常对象的引用重新压入调用这个方法的栈帧,这样就使得这个异常在掉用者中重新抛出。