异常 Exception 细节 面试题总结 MD

Markdown版本笔记我的GitHub首页我的博客我的微信我的邮箱
MyAndroidBlogsbaiqiantaobaiqiantaobqt20094baiqiantao@sina.com

目录

Exception 面试题总结

多线程 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 的关系

finally 语句是不是一定会被执行

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

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

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

正常情况下的执行顺序

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

测试案例:

System.out.println(test());

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

public String test2() {
    System.out.println("return statement"); //return语句执行之后才执行finally语句
    return "调用方法返回";
}

运行结果:

try block
finally block
直接返回

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

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

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

finally里也有return语句

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

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就变成不可到达语句了,也就是永远不能被执行到,所以需要注释掉否则编译器报错。

finally里修改返回值

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

测试用例:

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:

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不是一点作用没吗?请看下面

try中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");
        } 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 之前就返回吗?看下面。

catch中有return语句

当发生异常后,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异常可以即不必捕获也不抛出。

运行时异常
运行时异常我们一般不处理,当出现这类异常的时候程序会由虚拟机接管。比如,我们从来没有去处理过NullPointerException,而且这个异常还是最常见的异常之一。

出现运行时异常的时候,程序会将异常一直向上抛,一直抛到遇到处理代码,如果没有catch块进行处理,到了最上层,如果是多线程就有Thread.run()抛出,如果不是多线程那么就由main.run()抛出。抛出之后,如果是线程,那么该线程也就终止了,如果是主程序,那么该程序也就终止了。

其实运行时异常的也是继承自Exception,也可以用catch块对其处理,只是我们一般不处理罢了,也就是说,如果不对运行时异常进行catch处理,那么结果不是线程退出就是主程序终止。如果不想终止,那么我们就必须捕获所有可能出现的运行时异常。

两种异常的使用场景

  • 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);
        }
    }
}

checked 异常错误使用案例

public class Test {

    public static void main(String[] args) {
        try {
            start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void start() {
        System.out.println("这个方法并没有声明会抛出checked 异常,例如IOException");
    }
}

上面的代码编译是通不过的:

因为IOException是checked异常,而start方法并没有抛出IOException,编译器将在处理IOException时报错。

但是如果你将IOException改为Exception,编译器报错将消失,因为Exception可以用来捕捉所有运行时异常(包括unchecked异常),这样就不需要声明抛出语句。

将上例中的 IOException 改为 unchecked 异常也是可以的,例如改为 NullPointerException

其他小知识点

error和exception有什么区别

  • error表示系统级的错误,是java运行环境内部错误或者硬件问题,不能指望程序来处理这样的问题,除了退出运行外别无选择,它是Java虚拟机抛出的。
  • exception 表示程序需要捕捉、需要处理的异常,是由于程序设计的不完善而出现的问题,程序必须处理的问题。

final、finally、finalize的区别

  • final用于声明变量、方法和类的,分别表示变量值不可变,方法不可覆盖,类不可以继承
  • finally是异常处理中的一个关键字,表示finally{}里面的代码一定要执行
  • finalize是Object类的一个方法,在垃圾回收的时候会调用被回收对象的此方法。

常见的Exception和Error有哪些

  • 常见的 Checked 异常:ClassNotFoundExceptionCloneNotSupportedException,DataFormatException,IllegalAccessException,InterruptedExceptionIOExceptionNoSuchFieldExceptionNoSuchMethodException,ParseException,TimeoutException,XMLParseException
  • 常见的 Unchecked 异常:BufferOverflowException,ClassCastExceptionIllegalArgumentException,IllegalStateException,IndexOutOfBoundsException,NoSuchElementException,NullPointerException,SecurityException,SystemException,UnsupportedOperationException
  • 常见的 Error:OutOfMemoryErrorStackOverflowError、NoClassDefFoundError、UnsatisfiedLinkError、IOErrorThreadDeath、ClassFormatError、InternalError、UnknownError

2019-4-26

转载于:https://www.cnblogs.com/baiqiantao/p/10774657.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值