End

Java 异常捕获 总结 分享

本文地址


目录

Java 异常捕获 总结 分享

try catch 能捕获线程里面的异常吗?

try catch 中开启新的线程,能捕获线程里面的异常吗?

测试代码

try {
    new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println(1 / 0);//抛出了 unchecked exception,但是并没有 try catch
        }
    }).start();
} catch (Exception e) {
    System.out.println("这里能执行到吗?");
}
new Thread(new Runnable() {
    @Override
    public void run() {
        try {
            System.out.println(1 / 0);//正常 try catch 了 unchecked exception
        } catch (Exception e) {
            System.out.println("这里能执行到吗?");
        }
    }
}).start();

上面的是捕获不到异常的,下面的可以。

为什么

1、在java多线程中,所有线程都不允许抛出checked exception,也就是说各个线程的checked exception必须由自己捕获。这一点是通过java.lang.Runnable.run()方法声明进行约束的,因为此方法声明上没有throws部分。

public interface Runnable {
    public abstract void run();
}

知识点:

  • 子类的方法只能抛出父类的方法抛出的异常或者该异常的子类,而不能抛出该异常的父类或其他类型的异常
  • 如果父类的方法抛出多个异常,那么子类的该方法只能抛出父类该方法抛出的异常的子集
  • 如果父类的方法没有抛出异常,那么子类覆盖父类的该方法时也不能抛,如果有异常只能使用try catch语句

2、但是线程依然有可能抛出一些运行时的异常(即unchecked exception),当此类异常跑抛出时,此线程就会终结,而对于其他线程完全不受影响,且完全感知不到某个线程抛出的异常。

new Runnable() {
    @Override
    public void run() {
        throw new RuntimeException(); //可以抛 unchecked exception
    }
};

为什么这么设计

JVM的这种设计源自于这样一种理念:线程是独立执行的代码片断,线程的问题应该由线程自己来解决,而不要委托到外部。

因此,Java线程中的异常都应该在线程代码边界之内(run方法内)进行 try catch 并处理掉,我们不能捕获从线程中逃逸的异常。

其实使用 try catch 捕获异常时有一个规范,那就是尽量用 try catch 包住最少的代码,有些同学一上来就用 try catch 把整个方法的逻辑包住,这样非常不合适,比如就会导致上述 try catch 失效。

finally 语句是不是一定能被执行到?

问:Java异常捕获机制try...catch...finally块中的finally语句是不是一定会被执行?

答:不一定,至少有三种情况下finally语句是不会被执行的:

  • try语句没有被执行到。如在 try 语句之前就返回了,这样 finally 语句就不会执行,这也说明了 finally 语句被执行的必要而非充分条件是:相应的 try 语句一定被执行到。
  • JVM停止了。例如在 try 语句中抛异常之前System.exit(0)这样的语句,由于连 JVM 都停止了,所有都结束了,当然 finally 语句也不会被执行到。
  • 线程被 interrupted 的情况下,或者进程被杀死的情况下,finally语句也不会执行。

setXxHandler 的效果

基础设置

Thread.setDefaultUncaughtExceptionHandler((t, e) -> System.out.println("默认Handler捕获到了异常"));
Thread.currentThread().setUncaughtExceptionHandler((t, e) -> System.out.println("捕获到了异常"));
private static void test() throws ArithmeticException {
    System.out.println(1 / 0);
}

设置了 Handler,没有 try catch

System.out.println(1 / 0); //或test();
System.out.println("正常结束"); //不会执行

结论:

  • 默认Handler 当前线程的Handler没有 设置时
    • 会导致当前线程直接结束,也即后续的代码都不会再执行(不会执行最后的打印)
    • 会自动打印异常堆栈
  • 默认Handler 当前线程的Handler只有 一个 设置时
    • 都可以处理当前线程的异常
    • 同样会导致当前线程直接结束(不会执行最后的打印)
    • 不会自动打印异常堆栈,除非你在Handler中手动打印
  • 默认Handler 当前线程的Handler 设置时,只有当前线程的Handler会处理当前线程的异常

有 try catch 代码

try {
    System.out.println(1 / 0); //或test();
  System.out.println("try中不再继续执行");
} catch (ArithmeticException e) {
    System.out.println(捕获到了异常");
}
System.out.println("正常结束"); //会执行

结论:

  • 如果 try catch 住了异常,则任何Handler不会 处理当前线程的异常
  • 不会导致当前线程直接结束(会执行最后的打印),但是try中代码将不再继续执行
  • 不会自动打印异常堆栈,除非你在catch代码块中手动打印

finally 与 return 的执行顺序问题

1、正常情况下的执行顺序

finally语句是在try的return语句执行之后,return返回之前执行的

public class Test {
    public static void main(String[] args) {
        System.out.println(test());
    }

    public static String test() {
        try {
            System.out.println("try block");
            return test2();
        } finally {
            System.out.println("finally block");
        }
    }

    public static String test2() {
        System.out.println("return statement"); //return 语句执行之后才执行 finally 语句
        return "return value"; //finally 语句执行完毕以后才返回
    }
}

运行结果:

try block
return statement
finally block
return value

说明try 中的 return 语句先执行了,但并没有立即返回,而是等到 finally 执行结束后再返回

2、try、finally 里都有 return 语句

finally 块中的 return 语句会覆盖 try 块中的 return 语句返回

public class Test {
    public static void main(String[] args) {
        System.out.println(test());
    }

    public static String test() {
        try {
            System.out.println("try block");
            return test2();
        } finally {
            System.out.println("finally block");
            return "finally return value";
        }
        //return "normal return value";// finally 外面的 return 就变成不可到达语句,需要注释掉否则编译器报错
    }

    public static String test2() {
        System.out.println("try return statement"); //return 语句执行之后才执行 finally 语句
        return "try return value"; //finally 语句执行完以后,finally 里的 return 直接返回了
    }
}

运行结果:

try block
try return statement
finally block
finally return value

这说明 try 中的 return 语句虽然也执行了,但是finally 语句执行完以后,finally 里的 return 直接返回了,就不管try中是否还有返回语句。

3、finally 里修改 try 中的返回值

如果 finally 语句中没有 return 语句覆盖返回值,那么原来的返回值可能因为 finally 里的修改而改变

返回基本类型的情况

public class Test {
    public static void main(String[] args) {
        System.out.println(test());
    }

    public static int test() {
        int age = 20;
        try {
            return age += 80;
        } finally {
            age += 10;
        }
    }
}

运行结果:100

返回引用类型的情况-1

public class Test {
    public static void main(String[] args) {
        System.out.println(test().age);
    }

    public static Person test() {
        Person person = new Person();
        person.age = 20;
        try {
            person.age += 80;
            return person;
        } finally {
            person.age += 10;
        }
    }

    static class Person {
        int age;
    }
}

运行结果:110

返回引用类型的情况-2

public class Test {
    public static void main(String[] args) {
        System.out.println(test().age);
    }

    public static Person test() {
        Person person = new Person();
        person.age = 20;
        try {
            person.age += 80;
            return person;
        } finally {
            person = new Person();
            person.age += 10;
        }
    }

    static class Person {
        int age;
    }
}

运行结果:100

这其实就是Java到底是传值还是传址的问题了,简单来说就是:Java中只有传值没有传址

  • 不管是原始类型还是引用类型,传递的都是【副本】,也可以说是【值】
  • 如果参数类型是【原始类型】,那么传过来的就是这个参数的值,如果在函数中改变了副本的值不会改变原始的值
  • 如果参数类型是【引用类型】,那么传过来的就是这个引用参数的副本(对象的引用),这个副本存放的是参数的【地址】
    • 如果在函数中没有改变这个副本的地址,而是改变了地址中的值,那么在函数内的改变会改变到传入的参数;
    • 如果在函数中改变了副本的地址(如new一个新对象),那么副本就指向了一个新的地址,此时传入的参数还是指向原来的地址,所以不会改变到传入的参数。

4、finally 中有 return 时的 catch 语句

finally 中有 return 语句时,try 中抛出异常时的 catch 中的 return 执行情况,与未抛出异常时 try 中 return 的执行情况完全一样

public class Test {
    public static void main(String[] args) {
        System.out.println(test());
    }

    public static String test() {
        try {
            System.out.println("try block");
            int age = 1 / 0;
            return "try return " + age; //这里肯定执行不到的
        } catch (Exception e) {
            System.out.println("catch block");
            return test2();
        } finally {
            System.out.println("finally block");
            return "finally return value";
        }
        //return "normal return value";// finally 外面的 return 就变成不可到达语句,需要注释掉否则编译器报错
    }

    public static String test2() {
        System.out.println("catch return statement"); //return 语句执行之后才执行 finally 语句
        return "catch return value"; //finally 语句执行完以后,finally 里的 return 直接返回了
    }
}

运行结果是:

try block
catch block
catch return statement
finally block
finally return value

finally 中有 return 语句时,即使前面的 catch 块重新抛出了异常,则调用该方法的语句也不会获得 catch 块重新抛出的异常,而是会得到 finally 块的返回值,并且不会捕获异常

public class Test {
    public static void main(String[] args) {
        System.out.println(test());
    }

    public static String test() {
        try {
            System.out.println("try block");
            int age = 1 / 0;
            return "try return " + age; //这里肯定执行不到的
        } catch (Exception e) {
            System.out.println("catch block");
            int age = 1 / 0; //虽然这里的异常没被 try catch,但也不影响正常进入 finally 语句
            return "catch return " + age; //这里肯定也是执行不到的
        } finally {
            System.out.println("finally block");
            return "finally return value";
        }
        //return "normal return value";// finally 外面的 return 就变成不可到达语句,需要注释掉否则编译器报错
    }
}

运行结果是:

try block
catch block
finally block
finally return value

总结

  • finally 块在 try 或 catch 块中的 return 语句执行之后返回之前执行
  • 若 finally 里也有 return 语句,则覆盖 try 或 catch 中的 return 语句直接返回
  • finally 里的修改不影响 try 或 catch 中 return 已经确定的返回值(这句话需要好好理解一下)

换一种总结

try 中的 return 语句调用的函数先于 finally 中调用的函数执行,也就是说 try 中的 return 语句先执行,finally 语句后执行,但 try中的 return 并不是让函数马上返回结果,而是 return 语句执行后,将把返回结果放置进函数栈中,此时函数并不是马上返回,它要执行 finally 语句后才真正开始返回

但此时会出现两种情况:

  • 如果finally中也有return,则会直接返回finally中的return结果,并终止程序,函数栈中的return不会被完成
  • 如果finally中没有return,则在执行完finally中的代码之后,会将函数栈中保存的try return的内容返回并终止程序

注意:

  • 不管有没有出现异常,finally块中代码都会执行
  • 当try和catch中有return时,finally仍然会执行
  • finally是在try中return后面的表达式运算后执行的(此时并没有返回运算后的值,而是先把要返回的值保存起来,管finally中的代码怎么样,返回的值都不会改变,仍然是之前保存的值),所以函数返回值是在finally执行前确定的
  • finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值

权威解释

以下摘录自经典权威书籍:《The Java™ Programming Language, Fourth Edition》

a finally clause is always entered with a reason. 进入finally子句总是有原因的
That reason may be that the try code finished normally, 原因可能是try代码正常完成了
that it executed a control flow statement such as return, 也即它执行了例如return的控制流语句
or that an exception was thrown in code executed in the try block. 或在Try块中执行的代码抛出了异常
The reason is remembered when the finally clause exits by falling out the bottom. 当finally子句通过...结束时,原因被记住了

这里面说的 reason 有:try(或catch) 执行了 return 或抛出了异常
这里还说,finally 执行完毕以后,reason 是被记着呢(例如返回值是多少)

However, if the finally block creates its own reason to leave 如果finally块来创建自己离开的原因
by executing a control flow statement (such as break or return) or by throwing an exception, 通过执行控制流语句或引发异常
that reason supersedes the original one, and the original reason is forgotten. 则该原因将取代原始原因,并且原始原因将被遗忘。

这里面透露了一个非常重要的信息:如果 finally 块创建了自己的 reason,那么将会替换原始的的 reason
所以以上案例中,只要 finally 中有 return 语句,try、catch中的返回值或抛出的异常都都没有意义了

For example, consider the following code:

try {
    // … do something … 
    return 1;
} finally {
    return 2;
}
  • When the Try block executes its return, 当Try块执行返回时
  • the finally block is entered with the “reason” of returning the value 1. 进入finally块时将带有返回值1的“原因”
  • However, inside the finally block the value 2 is returned, 但是,在finally块内部返回了值2
  • so the initial intention is forgotten. 因此最初的意图已被遗忘

  • In fact, if any of the other code in the try block had thrown an exception, 实际上,如果try块中的任何其他代码引发了异常
  • the result would still be to return 2. 则结果仍将返回2
  • If the finally block did not return a value but simply fell out the bottom, 如果finally块没有返回值,而只是...
  • the “return the value 1 ″ reason would be remembered and carried out. 则“返回值1” 的原因会被记住并执行

2019-04-26

posted @ 2019-04-26 15:44  白乾涛  阅读(396)  评论(3编辑  收藏  举报