异常 Exception 知识点总结 MD

Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱
MyAndroidBlogs baiqiantao baiqiantao bqt20094 [email protected]

目录

多线程 try catch 异常捕获问题

一道面试题

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

例如:

try {
    new Thread(() -> System.out.println(1 / 0)).start(); //Runnable的run()方法抛出了 unchecked exception
    //new Thread(() -> throw new RuntimeException("抛出了 unchecked exception")).start();
} catch (Exception e) {
    e.printStackTrace();
    System.out.println("这里能执行到吗?"); //不可以
}

上面是捕获不到异常的,而如果改为下面这种形式,则可以捕获到异常:

new Thread(() -> {
    try {
        System.out.println(1 / 0); //Runnable的run()方法并没有抛出异常,而是自己捕获了异常
    } catch (Exception e) {
        e.printStackTrace();
        System.out.println("这里能执行到吗?"); //可以
    }
}).start();

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

结论

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

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

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

在Java中,线程方法的异常(无论是checked还是unchecked exception),都应该在线程代码边界之内(run方法内)进行try catch并处理掉。换句话说,我们不能捕获从线程中逃逸的异常。

JVM 处理机制

查看 Thread 的源码可以帮忙分析当线程出现未捕获异常时的处理逻辑。

首先看Thread.dispatchUncaughtException()方法:

//Dispatch an uncaught exception to the handler. This method is intended to be called only by the JVM.
private void dispatchUncaughtException(Throwable e) {
    getUncaughtExceptionHandler().uncaughtException(this, e);
}

这个方法仅仅被 JVM 调用,用来将 uncaught exception 分发到 handler 去处理,这个 handler 是哪来的呢?

public UncaughtExceptionHandler getUncaughtExceptionHandler() {
    return uncaughtExceptionHandler != null ?  uncaughtExceptionHandler : group;
}

Returns the handler invoked when this thread abruptly terminates due to an uncaught exception.

返回此线程由于未捕获的异常而突然终止时调用的handler。

If this thread has not had an uncaught exception handler explicitly set then this thread's ThreadGroup object is returned, unless this thread has terminated, in which case null is returned.

如果此线程没有显式设置未捕获的异常 handler,则返回此线程的 ThreadGroup 对象,除非此线程已终止,在这种情况下返回 null。

这里的uncaughtExceptionHandler只有一个地方初始化:

private volatile UncaughtExceptionHandler uncaughtExceptionHandler;// null unless explicitly set

public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
    checkAccess();
    uncaughtExceptionHandler = eh;
}

如果返回的是 ThreadGroup 的话,默认会一直找到顶层 ThreadGroup(类似双亲委派模型),然后会找 Thread 类共用的 defaultUncaughtExceptionHandler,如果存在则调用,如果不存在,则打印线程名字和异常:

public void uncaughtException(Thread t, Throwable e) {
    if (parent != null) {
        parent.uncaughtException(t, e);
    } else {
        Thread.UncaughtExceptionHandler ueh = Thread.getDefaultUncaughtExceptionHandler();
        if (ueh != null) {
            ueh.uncaughtException(t, e);
        } else if (!(e instanceof ThreadDeath)) { //不是ThreadDeath
            System.err.print("Exception in thread \""  + t.getName() + "\" "); //打印线程名字
            e.printStackTrace(System.err); //打印异常
        }
    }
}

defaultUncaughtExceptionHandler也只有一个地方初始化:

private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler;// null unless explicitly set

public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
    //检查权限
    defaultUncaughtExceptionHandler = eh;
}

也就是说,当一个线程中有未捕获的异常时,JVM 会通过调用 UncaughtExceptionHandler 的 uncaughtException 方法处理异常,如果没有设置,则会直接打印线程名字和异常。

finally 语句的执行与 return 的关系

Java异常捕获机制try...catch...finally块中的finally语句是不是一定会被执行?不一定,至少有两种情况下finally语句是不会被执行的:

  • try语句没有被执行到,如在try语句之前就返回了,这样finally语句就不会执行,这也说明了finally语句被执行的必要而非充分条件是:相应的try语句一定被执行到。
  • 在try块中有System.exit(0)这样的语句,System.exit(0)是终止Java虚拟机JVM的,连JVM都停止了,所有都结束了,当然finally语句也不会被执行到。

1、finally语句是在try的return语句执行之后,return返回之前执行的
测试案例:

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

    public static String test() {
        try {
            System.out.println("try block");
            if (new Random().nextBoolean()) {
                return "直接返回";
            } else {
                return test2();
            }
        } finally {
            System.out.println("finally block");
        }
    }

    public static String test2() {
        System.out.println("return statement");
        return "调用方法返回";
    }
}

运行结果:

try block
finally block
直接返回

try block
return statement
finally block
调用方法返回

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

这里大家可能会想:如果finally里也有return语句,那么是不是就直接返回了,try中的return就不能返回了?看下面。

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

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

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

运行结果:

try block
finally block
在finally中返回

这说明finally里的return直接返回了,就不管try中是否还有返回语句。

这里还有个小细节需要注意,finally里加上return过后,finally外面的return b就变成不可到达语句了,也就是永远不能被执行到,所以需要注释掉否则编译器报错。

3、如果finally语句中没有return语句覆盖返回值,那么原来的返回值可能因为finally里的修改而改变,也可能不变
测试用例:

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

    public static int test() {
        int b = 20;
        try {
            System.out.println("try block");
            return b += 80;
        } finally {
            b += 10;
            System.out.println("finally block");
        }
    }
}

运行结果:

try block
finally block
100

测试用例2:

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

    public static List<Integer> test() {
        List<Integer> list = new ArrayList<Integer>();
        list.add(10086);
        try {
            System.out.println("try block");
            return list;
        } finally {
            list.add(10088);
            System.out.println("finally block");
        }
    }
}

运行结果:

try block
finally block
[10086, 10088]

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

这里大家可能又要想:是不是每次返回的一定是try中的return语句呢?那么finally外的return不是一点作用没吗?请看下面

4、try块里的return语句在异常的情况下不会被执行,这样具体返回哪个看情况

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

    public static int test() {
        int b = 0;
        try {
            System.out.println("try block");
            b = b / 0;
            return b += 1;
        } catch (Exception e) {
            b += 10;
            System.out.println("catch block");
        } finally {
            b += 100;
            System.out.println("finally block");
        }
        return b;
    }
}

运行结果是:

try block
catch block
finally block
110

这里因 为在return之前发生了异常,所以try中的return不会被执行到,而是接着执行捕获异常的 catch 语句和最终的 finally 语句,此时两者对b的修改都影响了最终的返回值,这时最后的 return b 就起到作用了。

这里大家可能又有疑问:如果catch中有return语句呢?当然只有在异常的情况下才有可能会执行,那么是在 finally 之前就返回吗?看下面。

5、当发生异常后,catch中的return执行情况与未发生异常时try中return的执行情况完全一样

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

    public static int test() {
        int b = 0;
        try {
            System.out.println("try block");
            b = b / 0;
            return b += 1;
        } catch (Exception e) {
            b += 10;
            System.out.println("catch block");
            return 10086;
        } finally {
            b += 100;
            System.out.println("finally block");
        }
        //return b;
    }
}

运行结果:

try block
catch block
finally block
10086

说明了发生异常后,catch中的return语句先执行,确定了返回值后再去执行finally块,执行完了catch再返回,也就是说情况与try中的return语句执行完全一样。

总结:

  • finally块的语句在try或catch中的return语句执行之后返回之前执行
  • 且finally里的修改语句可能影响也可能不影响try或catch中return已经确定的返回值
  • 若finally里也有return语句则覆盖try或catch中的return语句直接返回

Checked 异常和 Unchecked 异常

Java包含两种异常:checked异常和unchecked异常:

  • Checked异常继承java.lang.Exception类,Checked异常必须通过try-catch被显式地捕获或者通过throws子句进行传递。
  • Unchecked异常继承自java.lang.RuntimeException类,是那些可能在 Java 虚拟机正常运行期间抛出的异常,unchecked异常可以即不必捕获也不抛出。

特点:

  • Checked和unchecked异常从功能的角度来讲是等价的,可以用checked异常实现的功能必然也可以用unchecked异常实现,反之亦然。
  • 选择checked异常还是unchecked异常是个人习惯或者组织规定问题。并不存在谁比谁强大的问题。
  • Unchecked异常避免了不必要的try-catch块,不会使代码显得杂乱;Unchecked异常不会因为异常声明聚集使方法声明显得杂乱。

Checked异常使用案例

public class Test {
    public static void main(String[] args) {

        try {
            new Test().testException();
        } catch (MyException e) {
            e.printStackTrace();
            System.out.println("调用抛出checked异常的方法时,同样必须通过try-catch显式地捕获或者通过throws子句进行传递");
        }
    }

    void testException() throws MyException {
        throw new MyException("抛出checked异常时,必须通过try-catch显式地捕获或者通过throws子句进行传递");
    }

    class MyException extends Exception {
        MyException(String s) {
            super(s);
        }
    }
}

unchecked异常使用案例
和上面相比,只需把自定义的异常由继承自Exception改为继承自RuntimeException即可。

由于RuntimeException继承自Exception,所以修改后上面其他代码都不需要改变就可以正常使用。但RuntimeException可以即不必捕获也不抛出:

public class Test {
    public static void main(String[] args) {
        new Test().testException();
    }

    void testException() {
        throw new MyException("unchecked异常可以即不必捕获也不抛出");
    }

    class MyException extends RuntimeException {
        MyException(String s) {
            super(s);
        }
    }
}

2019-4-26

猜你喜欢

转载自www.cnblogs.com/baiqiantao/p/10774657.html