一)java的异常处理机制(try…catch…finally)
先看一段代码
public class TestException {
@SuppressWarnings("finally")
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) {
TestException testException1 = new TestException();
try {
testException1.testEx();
} catch (Exception e) {
e.printStackTrace();
}
}
}
事实上,实际运行出来的答案与预想的有点不太一样
i=2
i=1
testEx2, catch exception
testEx2, finally; return value=false
testEx1, finally; return value=false
testEx, finally; return value=false
二)Try-catch概念理论
1)基本概念
例外是在程序运行过程中发生的异常事件,比如除0溢出、数组越界、文件找不到等,这些事件的发生将阻止程序的正常运行。为了加强程序的执行效率,程序设计时,必须考虑到可能发生的异常事件并做出相应的处理。C语言中,通过使用if语句来判断是否出现了例外,同时,调用函数通过被调用函数的返回值感知在被调用函数中产生的例外事件并进行处理。全程变量ErroNo常常用来反映一个异常事件的类型。但是,这种错误处理机制会导致不少问题。
Java通过面向对象的方法来处理例外。在一个方法的运行过程中,如果发生了例外,则这个方法生成代表该例外的一个对象,并把它交给运行时系统,运行时系统寻找相应的代码来处理这一例外。我们把生成例外对象并把它提交给运行时系统的过程称为抛弃(throw)一个例外。运行时系统在方法的调用栈中查找,从生成例外的方法开始进行回朔,直到找到包含相应例外处理的方法为止,这一个过程称为捕获(catch)一个例外。
2)源头Throwable
用面向对象的方法处理例外,就必须建立类的层次。类Throwable位于这一类层次的最顶层,只有它的后代才可以做为一个例外被抛弃。图1表示了例外处理的类层次。
从图中可以看出,类Throwable有两个直接子类:Error和Exception。Error类对象(如动态连接错误等),由Java虚拟机生成并抛弃(通常,Java程序不对这类例外进行处理);Exception类对象是Java程序处理或抛弃的对象。它有各种不同的子类分别对应于不同类型的例外。其中类RuntimeException代表运行时由Java虚拟机生成的例外,如算术运算例外ArithmeticException(由除0错等导致)、数组越界例外ArrayIndexOutOfBoundsException等;其它则为非运行时例外,如输入输出例外IOException等。Java编译器要求Java程序必须捕获或声明所有的非运行时例外,但对运行时例外可以不做处理。
3)异常处理关键字
Java的异常处理是通过5个关键字来实现的:try,catch,throw,throws,finally
1】Try
try语句用大括号{}指定了一段代码,该段代码可能会抛弃一个或多个例外。
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块。
三)try的嵌套
可以在一个成员函数调用的外面写一个try语句,在这个成员函数内部,写另一个try语句保护其他代码。每当遇到一个try语句,异常的框架就放到堆栈上面,直到所有的try语句都完成。如果下一级的try语句没有对某种异常进行处理,堆栈就会展开,直到遇到有处理这种异常的try语句。
参见如下例子:
public class ExceptionCatch {
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到异常时使用throwe;语句将异常抛出,那么main当然还是能够捕捉并处理这个procedure抛出来的异常。例如在procedure函数的catch中的System.out语句后面增加throwe;语句之后,执行结果就变为:
in procedure, catch ArithmeticException: java.lang.ArithmeticException: / by zero
in main, catch Exception: java.lang.ArithmeticException: / by zero
常见try-catch-finally嵌套使用方式public class TestCatch {
public static void main(String[] args) throws Exception {
txt2String(new File("D:test.txt"));
}
/**
* 本方法无法处理抛出的异常,处理某段代码抛出的异常,代码块中捕抓到的异常
* @param file
* @return
* @throws Exception
*/
public static String txt2String(File file) throws Exception {
StringBuilder result = new StringBuilder();
//代码块的总try-catch异常
try {
// 1.构造一个BufferedReader类来读取文件
BufferedReader br = new BufferedReader(new FileReader(file));
String s = null;
while ((s = br.readLine()) != null) {// 使用readLine方法,一次读一行
result.append(System.lineSeparator() + s);
}
//2代码块內嵌异常
try {
} catch (Exception e) {
}
} catch (Exception e) {
try {
//3代码块异常处理出现的异常
} catch (Exception e2) {
// TODO: handle exception
}
e.printStackTrace();
}finally {
//4.結束这段代码處理发生的异常
try {
} catch (Exception e2) {
// TODO: handle exception
}
}
return result.toString();
}
}
四)try-catch-finally程序块的执行流程以及执行结果
try-catch-finally程序块的执行流程以及执行结果比较复杂。
首先执行的是try语句块中的语句,这时可能会有以下三种情况:
1.如果try块中所有语句正常执行完毕,那么finally块的居于就会被执行,这时分为以下两种情况:
1】如果finally块执行顺利,那么整个try-catch-finally程序块正常完成。
2】如果finally块由于原因R突然中止,那么try-catch-finally程序块的结局是“由于原因R突然中止(completesabruptly)”
2.如果try语句块在执行过程中碰到异常V,这时又分为两种情况进行处理:
-->如果异常V能够被与try相应的catch块catch到,那么第一个catch到这个异常的catch块(也是离try最近的一个与异常V匹配的catch块)将被执行;这时就会有两种执行结果:
1>如果catch块执行正常,那么finally块将会被执行,这时分为两种情况:
1】如果finally块执行顺利,那么整个try-catch-finally程序块正常完成。
2】如果finally块由于原因R突然中止,那么try-catch-finally程序块的结局是“由于原因R突然中止(completesabruptly)”
2>如果catch块由于原因R突然中止,那么finally模块将被执行,分为两种情况:
1】如果如果finally块执行顺利,那么整个try-catch-finally程序块的结局是“由于原因R突然中止(completesabruptly)”。
2】如果finally块由于原因S突然中止,那么整个try-catch-finally程序块的结局是“由于原因S突然中止(completesabruptly)”,原因R将被抛弃。
(注意,这里就正好和我们的例子相符合,虽然我们在testEx2中使用throwe抛出了异常,但是由于testEx2中有finally块,而finally块的执行结果是completeabruptly的(别小看这个用得最多的return,它也是一种导致completeabruptly的原因之一啊——后文中有关于导致completeabruptly的原因分析),所以整个try-catch-finally程序块的结果是“completeabruptly”,所以在testEx1中调用testEx2时是捕捉不到testEx1中抛出的那个异常的,而只能将finally中的return结果获取到。
如果在你的代码中期望通过捕捉被调用的下级函数的异常来给定返回值,那么一定要注意你所调用的下级函数中的finally语句,它有可能会使你throw出来的异常并不能真正被上级调用函数可见的。当然这种情况是可以避免的,以testEx2为例:如果你一定要使用finally而且又要将catch中throw的e在testEx1中被捕获到,那么你去掉testEx2中的finally中的return就可以了。
这个事情已经在OMC2.0的MIB中出现过啦:服务器的异常不能完全被反馈到客户端。)
-->如果异常V没有catch块与之匹配,那么finally模块将被执行,分为两种情况:
1】如果finally块执行顺利,那么整个try-catch-finally程序块的结局就是“由于抛出异常V而突然中止”。
2】如果finally块由于原因S突然中止,那么整个try-catch-finally程序块的结局是“由于原因S突然中止”,异常V将被抛弃。
3.如果try由于其他原因R突然中止(completes abruptly),那么finally块被执行,分为两种情况:
1>如果finally块执行顺利,那么整个try-catch-finally程序块的结局是“由于原因R突然中止”。
2>如果finally块由于原因S突然中止,那么整个try-catch-finally程序块的结局是“由于原因S突然中止”,原因R将被抛弃。
五) try-catch-finally程序块中的return
从上面的try-catch-finally程序块的执行流程以及执行结果一节中可以看出无论try或catch中发生了什么情况,finally都是会被执行的,那么写在try或者catch中的return语句也就不会真正的从该函数中跳出了,它的作用在这种情况下就变成了将控制权(语句流程)转到finally块中;这种情况下一定要注意返回值的处理。
例如,catch 体里遇到 return 是怎么处理? finally 体遇到 return 怎么办?finally 体里有 System.exit() 方法怎么处理?当 catch 和 finally 体里同时遇上 return 怎么办?
或者,在try或者catch中return false了,而在finally中又return true,那么这种情况下不要期待你的try或者catch中的return false的返回值false被上级调用函数获取到,上级调用函数能够获取到的只是finally中的返回值,因为try或者catch中的return语句只是转移控制权的作用。
处理异常的时候不是每次都把它 throws 掉就完事了,很多时候异常是需要我们自己来 catch 并针对所抛出的 Exception 做一些后续的处理工作。
1)抛出Exception,没有finally,当 catch 遇上 return
/**
* @version 2017年11月15日 下午8:05:44
*/
public class CatchTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
catchTest();
}
// catch 后续处理工作
public static boolean catchMethod() {
System.out.print("call catchMethod and return --->> ");
return false;
}
// finally后续处理工作
public static void finallyMethod() {
System.out.println();
System.out.print("call finallyMethod and do something --->> ");
}
public static boolean catchTest() {
try {
int i = 10 / 0; // 抛出 Exception,后续处理被拒绝
System.out.println("i vaule is : " + i);
return true; // Exception 已经抛出,没有获得被执行的机会
} catch (Exception e) {
System.out.println(" -- Exception --");
return catchMethod(); // Exception 抛出,获得了调用方法并返回方法值的机会
}
}
}
结果:
-- Exception --
call catchMethod and return --->>
2)抛出 Exception,当 catch 体里有 return,finally 体的代码块将在catch 执行 return 之前被执行 public static boolean catchFinallyTest1() {
try {
int i = 10 / 0; // 抛出 Exception,后续处理被拒绝
System.out.println("i vaule is : " + i);
return true; // Exception 已经抛出,没有获得被执行的机会
} catch (Exception e) {
System.out.println(" -- Exception --");
return catchMethod(); // Exception 抛出,获得了调用方法的机会,但方法值在 finally 执行完后才返回
} finally {
finallyMethod(); // Exception 抛出,finally 代码块将在 catch 执行 return 之前被执行
}
}
结果
-- Exception --
call catchMethod and return --->>
call finallyMethod and do something --->>
3)不抛 Exception,当 finally 代码块里面遇上return,finally 执行完后将结束整个方法
public static boolean catchFinallyTest2() {
try {
int i = 10 / 2; // 不抛出 Exception
System.out.println("i vaule is : " + i);
return true; // 获得被执行的机会,但执行需要在 finally 执行完成之后才能被执行
} catch (Exception e) {
System.out.println(" -- Exception --");
return catchMethod();
} finally {
finallyMethod();
return false; // finally 中含有 return 语句,这个 return 将结束这个方法,不会在执行完之后再跳回 try 或 catch
// 继续执行,方法到此结束,返回 false
}
}
结果
i vaule is : 5
call finallyMethod and do something --->>
4)不抛 Exception,当 finally 代码块里面遇上System.exit() 方法将结束和终止整个程序,而不只是方法 public static boolean finallyExitTest() {
try {
int i = 10 / 2; // 不抛出 Exception
System.out.println("i vaule is : " + i);
return true; // 获得被执行的机会,但由于 finally 已经终止程序,返回值没有机会被返回
} catch (Exception e) {
System.out.println(" -- Exception --");
return true;
} finally {
finallyMethod();
System.exit(0);// finally 中含有 System.exit() 语句,System.exit() 将退出整个程序,程序将被终止
}
}
结果:
i vaule is : 5
call finallyMethod and do something --->>
5)抛出Exception,当 catch 和 finally 同时遇上 return,catch 的 return 返回值将不会被返回,finally的 return 语句将结束整个方法并返回
public static boolean finallyTest1() {
try {
int i = 10 / 0; // 抛出 Exception,后续处理被拒绝
System.out.println("i vaule is : " + i);
return true; // Exception 已经抛出,没有获得被执行的机会
} catch (Exception e) {
System.out.println(" -- Exception --");
return true; // Exception 已经抛出,获得被执行的机会,但返回操作将被 finally 截断
} finally {
finallyMethod();
return false; // return 将结束整个方法,返回 false
}
}
结果:
-- Exception --
call finallyMethod and do something --->>
6)不抛出 Exception,当 finally 遇上 return,try 的 return 返回值将不会被返回,finally的 return 语句将结束整个方法并返回
public static boolean finallyTest2() {
try {
int i = 10 / 2; // 不抛出 Exception
System.out.println("i vaule is : " + i);
return true; // 获得被执行的机会,但返回将被 finally 截断
} catch (Exception e) {
System.out.println(" -- Exception --");
return true;
} finally {
finallyMethod();
return false; // return 将结束这个方法,不会在执行完之后再跳回 try 或 catch 继续执行,返回 false
}
}
结果
i vaule is : 5
call finallyMethod and do something --->>
总结:方法需要返回值的java 的异常处理中,在不抛出异常的情况下,程序执行完 try 里面的代码块之后,该方法并不会立即结束,而是继续试图去寻找该方法有没有 finally 的代码块:
1】如果没有 finally 代码块,整个方法在执行完 try 代码块后返回相应的值来结束整个方法;
2】如果有 finally 代码块,此时程序执行到 try 代码块里的 return 语句之时并不会立即执行 return,而是先去执行 finally 代码块里的代码,
1)若 finally 代码块里没有return 或没有能够终止程序的代码,程序将在执行完 finally 代码块代码之后再返回 try 代码块执行 return 语句来结束整个方法;
2)若 finally 代码块里有 return 或含有能够终止程序的代码,方法将在执行完 finally 之后被结束,不再跳回 try 代码块执行 return。
在抛出异常的情况下,原理也是和上面的一样的,把上面的 try 换成 catch 去理解即可。
参考:1)http://www.blogjava.net/fancydeepin/archive/2012/07/08/java_try-catch-finally.html
2)http://blog.youkuaiyun.com/idulx/article/details/7332588