1. 异常丢失
1.1 在finally子句中抛出异常。
class MyException1 extends Exception{
public String toString(){return "测试异常————test1";}
}
class MyException2 extends Exception{
public String toString(){return "测试异常————test2";}
}
public class TestException {
public void test1() throws MyException1{throw new MyException1();}
public void test2() throws MyException2{throw new MyException2();}
public static void main(String[] args) throws MyException1, MyException2 {
TestException c = new TestException();
try {
System.out.println("----------------------外层try------------------------------");
try{
System.out.println("----------------------内层try------------------------------");
c.test1();
}finally{
System.out.println("----------------------内层finally------------------------------");
c.test2();
}
}catch(Exception e) {
System.out.println("----------------------外层catch------------------------------");
e.printStackTrace();
}
}
}
输出:
----------------------外层try------------------------------
----------------------内层try------------------------------
----------------------内层finally------------------------------
----------------------外层catch------------------------------
测试异常————test2
at person.xsc.datamanage.TestException.test2(TestException.java:10)
at person.xsc.datamanage.TestException.main(TestException.java:20)
class MyException1 extends Exception{
public String toString(){return "测试异常————test1";}
}
class MyException2 extends Exception{
public String toString(){return "测试异常————test2";}
}
public class TestException {
public void test1() throws MyException1{throw new MyException1();}
public void test2() throws MyException2{throw new MyException2();}
public static void main(String[] args) throws MyException1, MyException2 {
TestException c = new TestException();
try{
c.test1();
}catch(Exception e) {
e.printStackTrace();
}
}
}
输出:
测试异常————test1
at person.xsc.datamanage.TestException.test1(TestException.java:9)
at person.xsc.datamanage.TestException.main(TestException.java:14)
从上面两次代码运行可以看出,内层try抛出的异常被内层finally抛出的异常覆盖掉了。所以,不要在finally子句中抛出异常。
1.2 在finally子句中返回(return)。
package person.xsc.datamanage;
public class TestException {
public static void main(String[] args) {
try {
System.out.println("----------------------try层------------------------------");
throw new Exception("exception b");
}finally{
System.out.println("----------------------finally层------------------------------");
return ;
}
}
}
输出:
----------------------try层------------------------------
----------------------finally层------------------------------
此时会发现try和finally块我们都执行了,但是明明我们在try块抛出了异常,但是最后却没有显示任何异常抛出。所以,如果finally块中包含了return语句,即使try块抛出了异常,但是最后会得到finally块的返回值,并且不会捕获异常。
1.3 观察try catch finally里面try catch finally嵌套执行顺序
package person.xsc.datamanage;
public class TestException {
private static void errorMethod(){
try{
System.out.println("----------------------内层try------------------------------");
int i = 0;
int a = 100/i;
}catch (Exception e){
System.out.println("----------------------内层catch" + e.getMessage() + "------------------------------");
}finally {
System.out.println("----------------------内层finally------------------------------");
}
}
public static void main(String[] args){
try{
System.out.println("----------------------外层try------------------------------");
errorMethod();
}catch (Exception e){
System.out.println("----------------------外层catch" + e.getMessage() + "------------------------------");
}finally {
System.out.println("----------------------外层finally------------------------------");
}
}
}
输出:
----------------------外层try------------------------------
----------------------内层try------------------------------
----------------------内层catch/ by zero------------------------------
----------------------内层finally------------------------------
----------------------外层finally------------------------------
内层catch处理了异常,所以没有执行外层catch。这边可以尝试把内层catch注释掉再跑一遍,会发现外层catch会进行异常处理。所以,try catch嵌套,内层不能捕获时,会考虑外层内否捕获,内层能捕获,则外层catch不执行。
1.4 观察 try catch里面嵌套try finally,在try finally里面再嵌套try finally
package person.xsc.datamanage;
class MyException1 extends Exception{
public String toString(){return "测试异常————test1";}
}
class MyException2 extends Exception{
public String toString(){return "测试异常————test2";}
}
class MyException3 extends Exception{
public String toString(){return "测试异常————test3";}
}
public class TestException {
public void test1() throws MyException1{throw new MyException1();}
public void test2() throws MyException2{throw new MyException2();}
public void test3() throws MyException3{throw new MyException3();}
public static void main(String[]agrs){
TestException mt=new TestException() ;
try{
System.out.println("----------------------外层try------------------------------");
try{
System.out.println("----------------------中间层try------------------------------");
try{
System.out.println("----------------------内层try------------------------------");
mt.test1();
}finally{
System.out.println("----------------------内层finally------------------------------");
mt.test2() ;
}
}finally{
System.out.println("----------------------中间层finally------------------------------");
mt.test3() ;
}
}catch(Exception ex){
System.out.println("----------------------外层catch------------------------------");
ex.printStackTrace();
}
}
}
输出:
----------------------外层try------------------------------
----------------------中间层try------------------------------
----------------------内层try------------------------------
----------------------内层finally------------------------------
----------------------中间层finally------------------------------
----------------------外层catch------------------------------
测试异常————test3
at person.xsc.datamanage.TestException.test3(TestException.java:14)
at person.xsc.datamanage.TestException.main(TestException.java:30)
观察上面的代码,能发现当多个finally块都有异常抛出时,抛出最外层的finally的异常。
1.5 建议
- 永远不要在finally中抛出异常;
- 切忌不要在finall语块中使用return。因为try块中的return值会先保存起来,然后执行完finally中的代码后,才会把try块中的return值返回,所以finally中的代码逻辑是不会影响try块中的return值的。但如果在finally中使用return了就会导致try块中的代码得不到执行而无法返回正确的结果。
- 使用try...finally块时要格外小心,如果可以的话,尽量使用完整的try...catch...finally;
- 尽量捕获特定的子类,而不是直接捕获Exception类;
- 不要在catch块中吞掉异常。即对异常不处理,直接return空;
2. 异常链
2.1 异常链定义
两个或者多个不同的异常出现在同一个程序中,并且会发生嵌套抛出,称之为异常链。简单的来说,就是捕获一个异常后抛出另一个异常,并且把原始异常信息保存下来。
2.2 异常链存在意义
随着项目开发的规模越来越大,越往底层,可能抛出的异常类型也会越来越多。如果在上层想要处理这些异常,就需要挨个的写很多catch语句块来捕捉异常,这样是很麻烦的;如果我们对底层抛出的异常捕获后,抛出一个新的统一的异常,的确可以避免这个问题。但是直接抛出一个新的异常,又可能会造成最原始的异常信息丢失,不利于排查问题。因此,如果采用异常链,在保有底层异常信息的基础上,将多层次异常以链路方式进行封装,对后续追查定位BUG是非常有利的。
2.3 异常链实例
package person.xsc.datamanage;
class MyException1 extends Exception{
public MyException1(){
super("这是个异常");
}
}
public class TryDemoFive {
public static void test1() throws MyException1{
throw new MyException1();
}
public static void test2() throws Exception{
try {
test1();
} catch (MyException1 e) {
throw new Exception("我是新产生的异常1",e);
}
}
public static void test3() throws Exception{
try {
test2();
} catch (Exception e) {
Exception e1=new Exception("我是新产生的异常2");
e1.initCause(e);// //实例化异常原因
throw e1;
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
try {
test3();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
输出:
java.lang.Exception: 我是新产生的异常2
at person.xsc.datamanage.TryDemoFive.test3(TryDemoFive.java:22)
at person.xsc.datamanage.TryDemoFive.main(TryDemoFive.java:31)
Caused by: java.lang.Exception: 我是新产生的异常1
at person.xsc.datamanage.TryDemoFive.test2(TryDemoFive.java:15)
at person.xsc.datamanage.TryDemoFive.test3(TryDemoFive.java:20)
... 1 more
Caused by: person.xsc.datamanage.MyException1: 这是个异常
at person.xsc.datamanage.TryDemoFive.test1(TryDemoFive.java:9)
at person.xsc.datamanage.TryDemoFive.test2(TryDemoFive.java:13)
... 2 more
2.4 异常链传递过程中,使用Throw带参构造方法和initCause区别?
使用异常的根类Throw所提供的带参构造方法Throwable(String message,Throwable cause)和初始化方法initCause(Throwable cause)都可以实现异常链信息传递。
区别在于initCause方法更加灵活,可以在异常对象构造完成后单独进行异常信息的赋值,在对于异常信息传递作用而言,二者没有区别。
注意A.initCause(B) 语句中A是新抛出的异常,B是捕捉之前方法传入的异常,别搞混淆
3.关于异常的面试问题
如果执行finally代码块之前方法返回了结果,或者JVM退出了,finally块中的代码还会执行吗? 只有在try里面是有System.exit(0)来退出JVM的情况下finally块中的代码才不会执行。
说出下面代码存在的问题
public class TestException { public static void start() throws IOException, RuntimeException{ throw new RuntimeException("Not able to Start"); } public static void main(String args[]) { try { start(); } catch (Exception ex) { ex.printStackTrace(); } catch (RuntimeException re) { re.printStackTrace(); } } }
这段代码会在捕捉异常代码块的RuntimeException类型变量“re”里抛出编译异常错误。因为Exception是RuntimeException的超类,在start方法中所有的RuntimeException会被第一个捕捉异常块捕捉,这样就无法到达第二个捕捉块,这就是抛出“exception java.lang.RuntimeException has already been caught”的编译错误原因。所以,如果父类的 catch 块出现在子类的catch 块之前, 就会导致编译错误。因为子类的catch块永远也没有机会运行。
说出下面代码存在的问题
public class TestException { public static void start() throws IOException{ throw new RuntimeException("Not able to Start"); } }
public class Start2 extends TestException{
public static void start() throws Exception{
throw new Exception("Not able to Start");
}
}
这段代码编译器将对子类覆盖start方法产生不满。因为每个Java中方法的覆盖是有规则的,一个覆盖的方法不能抛出的异常比原方法继承关系高。因为这里的start方法在超类中抛出了IOException,所有在子类中的start方法只能抛出要么是IOExcepition或是其子类,但不能是其超类,如Exception。
- **说出下面代码存在的问题**
public class TestException {
public static void start(){
System.out.print("Not able to Start");
}
public static void main(String args[]) {
try {
start();
}catch(IOException e) {
e.printStackTrace();
}
}
}
``` 这段代码编译器在处理IOException时会报错,因为没有声明抛出语句,需要start方法进行throws IOException。