Java如何处理异常
如下程序代码编译并运行结果是什么?请选出正确的答案。
public class Test { public Test() { } boolean testEx() throws Exception { boolean ret = true; try { ret = testEx1(); } catch (Exception e) { System.out.println("testEx, catch exception"); ret = false; throw e; } finally { System.out.println("testEx, finally; return value=" + ret); } return ret; } boolean testEx1() throws Exception { boolean ret = true; try { ret = testEx2(); if (!ret) { return false; } System.out.println("testEx1, at the end of try"); return ret; } catch (Exception e) { System.out.println("testEx1, catch exception"); ret = false; throw e; } finally { System.out.println("testEx1, finally; return value=" + ret); return ret; } } boolean testEx2() throws Exception { boolean ret = true; try { int b = 12; int c; for (int i = 2; i >= -2; i--) { c = b / i; System.out.println("i=" + i); } return true; } catch (Exception e) { System.out.println("testEx2, catch exception"); ret = false; throw e; } finally { System.out.println("testEx2, finally; return value=" + ret); return ret; } } public static void main(String[] args) { Test testException1 = new Test(); try { testException1.testEx(); } catch (Exception e) { e.printStackTrace(); } } }
(a)编译正常,输出结果如下:
i=2 i=1 testEx2, catch exception testEx2, finally; return value=false testEx1, catch exception testEx1, finally; return value=false testEx, catch exception testEx, finally; return value=false
(b)不能正常编译。
(c)编译正常,输出结果如下:
i=2 i=1 testEx2, catch exception testEx2, finally; return value=false testEx1, finally; return value=false testEx, finally; return value=false
(d)编译正常,输出结果如下:
i=1 i=2 testEx2, catch exception testEx2, finally; return value=false testEx1, catch exception testEx1, finally; return value=false testEx, catch exception testEx, finally; return value=false
考点:考察求职者对于try、catch和finally的理解,作为一个Java程序员,必须对这个知识点达到精通的程度,才能坦然面对面试中的考察。更为重要的是,在将来软件项目开发中,能使开发的程序更加健壮。
出现频率:★★★★★
【面试题解析】
程序运行过程中会发生各种异常事件,例如除0溢出、数组越界、文件找不到等,这些事件的发生将阻止程序的正常运行。为了加强程序的健壮性,程序设计时,必须考虑到可能发生的异常事件,并做出相应的处理。
Java通过面向对象的方法来处理异常。在一个方法的运行过程中,如果发生了异常,则这个方法生成代表该异常的一个对象,并把它交给运行时系统,运行时系统寻找相应的代码来处理这一异常。
通常把生成异常对象并把它提交给运行时系统的过程称为抛出(throw)一个异常。运行时系统在方法的调用栈中查找,从生成异常的方法开始进行回朔,直到找到包含相应异常处理的方法为止,这一个过程称为捕获(catch)一个异常。
Java的异常处理是通过5个关键字来实现的:try、catch、throw、throws、finally。
1.try
try语句用大括号{}指定了1段代码,该段代码可能会抛出一个或多个异常。
2.catch
catch语句的参数类似于方法的声明,包括一个异常类型和一个异常对象。异常类型必须为Throwable类的子类,它指明了catch语句所处理的异常类型,异常对象则由运行时系统在try所指定的代码块中生成并被捕获,大括号中包含对象的处理,其中可以调用对象的方法。
catch语句可以有多个,分别处理不同类型的异常。Java运行时系统从上到下分别对每个catch语句处理的异常类型进行检测,直到找到类型相匹配的catch语句为止。
注意:类型匹配指catch所处理的异常类型与生成的异常对象的类型完全一致或者是它的父类,因此,catch语句的排列顺序应该是从特殊到一般。
也可以用一个catch语句处理多个异常类型,这时它的异常类型参数应该是这多个异常类型的父类,程序设计中要根据具体的情况来选择catch语句的异常处理类型。
3.finally
try所限定的代码中,当抛出一个异常时,其后的代码不会被执行。通过finally语句可以指定一块代码。无论try所指定的程序块中抛出或不抛出异常,也无论catch语句的异常类型是否与所抛出的异常的类型一致,finally所指定的代码都要被执行,它提供了统一的出口。通常在finally语句中可以进行资源的清除工作,例如关闭打开的文件等。
4.throws
throws总是出现在一个函数头中,用来标明该成员函数可能抛出的各种异常。对大多数Exception子类来说,Java编译器会强迫你声明在一个成员函数中抛出的异常的类型。如果异常的类型是Error或RuntimeException,或它们的子类,这个规则不起作用,因为这在程序的正常部分中是不期待出现的。如果想明确地抛出一个RuntimeException,必须用throws语句来声明它的类型。
5.throw
throw总是出现在函数体中,用来抛出一个异常。程序会在throw语句后立即终止,后面的语句将执行不到,然后在包含它的所有try块中(可能在上层调用函数中),从里向外寻找含有与其匹配的catch子句的try块。
6.try的嵌套
你可以在一个成员函数调用的外面写一个try语句,在这个成员函数内部,写另一个try语句,保护其他代码。每当遇到一个try语句,异常的框架就放到堆栈上面,直到所有的try语句都完成。如果下一级的try语句没有对某种异常进行处理,堆栈就会展开,直到遇到有处理这种异常的try语句。下面是一个try语句嵌套的例子,代码如下:
class MyTest { static void procedure() { try { int a = 0; int b = 42 / a; } catch (java.lang.ArithmeticException e) { System.out.println("in procedure, catch ArithmeticException: " + e); } } public static void main(String args[]) { try { procedure(); } catch (java.lang.Exception e) { System.out.println("in main, catch Exception: " + e); } } }
这个例子执行的结果为:
in procedure, catch ArithmeticException: java.lang.ArithmeticException: / by zero
成员函数procedure()里有自己的try/catch控制,所以main()方法不用去处理ArrayIndexOutOfBoundsException。当然如果在procedure()函数中catch到异常时使用“throw e;”语句将异常抛出,那么main()方法当然还是能够捕捉并处理这个procedure()抛出来的异常。例如在procedure()函数的catch中的System.out语句后面增加“throw e;”语句之后,执行结果就变为:
in procedure, catch ArithmeticException: java.lang.ArithmeticException: / by zero in main, catch Exception: java.lang.ArithmeticException: / by zero
7.try-catch程序块的执行流程以及执行结果
相对于try-catch-finally程序块而言,try-catch的执行流程以及执行结果还是比较简单的。首先执行的是try语句块中的语句,这时可能会有以下2种情况。
• 如果try块中所有语句正常执行完毕,就不会有其他的动作被执行,整个try-catch程序块正常完成。
• 如果try语句块在执行过程中碰到异常,这时又分为以下两种情况进行处理。
如果异常能够被与try相应的catch语句块匹配,那么第一个捕获到这个异常的catch语句块(也是离try最近的一个与异常匹配的catch块)将被执行。
如果异常没有catch块与之匹配,那么这个try-catch程序块的结果就是“由于抛出异常而突然中止”。
8.try-catch-finally程序块的执行流程以及执行结果
try-catch-finally程序块的执行流程以及执行结果比较复杂。首先执行的是try语句块中的语句,这时可能会有以下3种情况。
(1)如果try块中所有语句正常执行完毕,那么finally块就会被执行。
(2)如果try语句块在执行过程中碰到异常,这时又分为以下两种情况进行处理。
• 如果异常能够被与try相应的catch块catch到,那么第一个catch到这个异常的catch块(也是离try最近的一个与异常V匹配的catch块)将被执行。
注意:这里就正好和面试题相符合,虽然我们在testEx2中使用throw e抛出了异常,但是由于testEx2中有finally块,而finally块的执行结果是突然中止的,所以整个try-catch-finally程序块的结果是“突然中止”,所以在testEx1中调用testEx2时是捕捉不到testEx1中抛出的那个异常的,而只能将finally中的return结果获取到。
• 如果异常没有catch块与之匹配,那么finally模块将被执行。
(3)如果try由于其他原因突然中止,那么finally块被执行。
9.try-catch-finally程序块中的return
无论try或catch中发生了什么情况,finally都是会被执行的,写在try或者catch中的return语句也就不会真正从该函数中跳出,它的作用在这种情况下就变成了将控制权(语句流程)转到finally块中;这种情况下一定要注意返回值的处理。
例如,在try或者catch中return false,而在finally中又return true,那么这种情况下不要期待try或者catch中的return false的返回值false被上级调用函数获取到,上级调用函数能够获取到的只是finally中的返回值,因为try或者catch中的return语句只是转移控制权的作用。
10.如何抛出异常
程序员如果知道某个函数有可能抛出异常,又不想在这个函数中对异常进行处理,只是想把它抛出去让调用这个函数的上级调用函数进行处理,那么有以下3种方式可供选择。
(1)直接在函数头中throws SomeException,函数体中不需要try/catch。比如将最开始的面试题中的testEx2改为下面的方式,那么testEx1就能捕捉到testEx2抛出的异常,代码如下:
boolean testEx2() throws Exception{ boolean ret = true; int b=12; int c; for (int i=2;i>=-2;i--){ c=b/i; System.out.println("i="+i); } return true; }
(2)使用try/catch,在catch中进行一定的处理之后(如果有必要的话)抛出某种异常。例如上面的testEx2改为下面的方式,testEx1也能捕获到它抛出的异常,代码如下:
boolean testEx2() throws Exception{ boolean ret = true; try{ int b=12; int c; for (int i=2;i>=-2;i--){ c=b/i; System.out.println("i="+i); } return true; }catch (Exception e){ System.out.println("testEx2, catch exception"); Throw e; } }
(3)使用try/catch/finally,在catch中进行一定的处理之后(如果有必要的话)抛出某种异常。例如上面的testEx2改为下面的方式,testEx1也能捕获到它抛出的异常,代码如下:
boolean testEx2() throws Exception{ boolean ret = true; try{ int b=12; int c; for (int i=2;i>=-2;i--){ c=b/i; System.out.println("i="+i); throw new Exception("aaa"); } return true; }catch (java.lang.ArithmeticException e){ System.out.println("testEx2, catch exception"); ret = false; throw new Exception("aaa"); }finally{ System.out.println("testEx2, finally; return value="+ret); } }
11.对Java异常处理的建议
弄清楚try-catch-finally的执行情况后才能正确使用它。如果程序员使用的是try-catch-finally语句块,而又需要保证有异常时能够抛出异常,那么在finally语句中就不要使用return语句(finally语句块最重要的作用应该是是释放资源),因为finally中的return语句会导致用户的throw e被抛出,在这个try-catch-finally的外面将只能看到finally中的返回值(除非在finally中抛出异常)。
参考答案:(c)。