• Java 异常捕获 总结 分享 [MD]


    博文地址

    我的GitHub我的博客我的微信我的邮箱
    baiqiantao baiqiantao bqt20094 baiqiantao@sina.com

    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语句也不会执行。

    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 已经确定的返回值(这句话需要好好理解一下)

    权威解释

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

    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

  • 相关阅读:
    WPF 模板(二)
    WPF 模板
    WFP 样式(复习用)
    wpf 特效学习
    MVC 开源控件学习
    设计模式学习
    使用带参数方式新增或修改可为空的非字符串类型数据到oralce数据库
    python(13)- 文件处理应用Ⅱ:增删改查
    051孤荷凌寒从零开始学区块链第51天DAPP006
    050孤荷凌寒从零开始学区块链第50天DAPP003
  • 原文地址:https://www.cnblogs.com/baiqiantao/p/10774657.html
Copyright © 2020-2023  润新知