异常 Exception 细节 面试题总结 MD

Markdown版本笔记 个人GitHub首页 个人博客 个人微信 个人邮箱
MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina.com

目录

Exception 面试题总结

多线程 try catch 异常捕获问题

一道面试题

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

例如:git

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("这里能执行到吗?"); //不能够
}

上面是捕获不到异常的,而若是改成下面这种形式,则能够捕获到异常:github

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的这种设计源自于这样一种理念:线程是独立执行的代码片段,线程的问题应该由线程本身来解决,而不要委托到外部。less

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

JVM 处理机制

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

首先看Thread.dispatchUncaughtException()方法:this

//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

相关文章
相关标签/搜索