Java的异常算是Java语言的一个特点了。也是在平常编码中会常常使用到的东西。但你真的了解异常吗?java
这里有一些关于异常的经典面试题:面试
若是以上问题的答案你都能了然与胸,那么恭喜你,已经很熟悉Java异常这一块了。ide
若是一些问题还弄不清楚?不要紧,看完这篇文章就能够了。函数
先上图编码
抛开下面那些异常不谈,咱们的关注点可能主要在四个类上:spa
其中,由于 Error
表明“错误”,多为比较严重的错误。若是你了解 JVM ,应该对 OutOfMemoryError
和 StackOverflowError
这两个类比较熟悉。code
通常咱们在写代码时,可能用的比较多的是 Exception
类和 RuntimeException
类。orm
那究竟是继承 Exception
类好仍是继承 RuntimeException
类好呢?后面咱们在“编写异常的最佳实践”小节会讲到。对象
Java7对异常作了两个改进。第一个是 try-with-resources ,第二个是 catch多个异常 。继承
所谓的try-with-resources,是个语法糖。实际上就是自动调用资源的close()函数。和Python里的with语句差很少。
不使用try-with-resources,咱们在使用io等资源对象时,一般是这样写的:
String getReadLine() throws IOException { BufferedReader br = new BufferedReader(fileReader); try { return br.readLine(); } finally { if (br != null) br.close(); } } 复制代码
使用try-with-recources的写法:
String getReadLine() throws IOException { try (BufferedReader br = new BufferedReader(fileReader)) { return br.readLine(); } } 复制代码
显然,编绎器自动在try-with-resources后面增长了判断对象是否为null,若是不为null,则调用close()函数的的字节码。
只有实现了java.lang.AutoCloseable接口,或者java.io.Closable(实际上继随自java.lang.AutoCloseable)接口的对象,才会自动调用其close()函数。
有点不一样的是java.io.Closable要求一实现者保证close函数能够被重复调用。而AutoCloseable的close()函数则不要求是幂等的。具体能够参考Javadoc。
可是,须要注意的是try-with-resources会出现 异常覆盖 的问题,也就是说 catch
块抛出的异常可能会被调用 close()
方法时抛出的异常覆盖掉。咱们会在下面的小节讲到异常覆盖。
直接上代码:
public static void main(String[] args) { try { int a = Integer.parseInt(args[0]); int b = Integer.parseInt(args[1]); int c = a / b; System.out.println("result is:" + c); } catch (IndexOutOfBoundsException | NumberFormatException | ArithmeticException ie) { System.out.println("发生了以上三个异常之一。"); ie.getMessage(); // 捕捉多异常时,异常变量默认有final修饰, // 因此下面代码有错: // ie = new ArithmeticException("test"); } } 复制代码
若是 catch
块和 finally
块都抛出了异常怎么办?请看下下小节分析。
所谓运行时异常指的是 RuntimeException
,你不用去显式的捕捉一个运行时异常,也不用在方法上声明。
反之,若是你的异常只是一个 Exception
,它就须要显式去捕捉。
示例代码:
void test() { hasRuntimeException(); try { hasException(); } catch (Exception e) { e.printStackTrace(); } } void hasException() throws Exception { throw new Exception("exception"); } void hasRuntimeException() { throw new RuntimeException("runtime"); } 复制代码
虽然从异常的结构图咱们能够看到, RuntimeException
继承自 Exception
。但Java会“特殊对待”运行时异常。因此若是你的程序里面须要这类异常时,能够继承 RuntimeException
。
并且若是不是明确要求要把异常交给上层去捕获处理的话,咱们建议是 优先使用运行时异常 ,由于它会让你的代码更加简洁。
正如咱们前面提到的,在 finally
块调用资源的 close()
方法时,是有可能抛出异常的。与此同时咱们可能在 catch
块抛出了另外一个异常。那么 catch
块抛出的异常就会被 finally
块的异常“吃掉”。
看看这段代码,调用 test()
方法会输出什么?
void test() { try { overrideException(); } catch (Exception e) { System.out.println(e.getMessage()); } } void overrideException() throws Exception { try { throw new Exception("A"); } catch (Exception e) { throw new Exception("B"); } finally { throw new Exception("C"); } } 复制代码
会输出 C
。能够看到,在 catch
块的 B
被吃掉了。
JDK提供了Suppressed的两个方法来解决这个问题:
// 调用test会输出: // C // A void test() { try { overrideException(); } catch (Exception e) { System.out.println(e.getMessage()); Arrays.stream(e.getSuppressed()) .map(Throwable::getMessage) .forEach(System.out::println); } } void overrideException() throws Exception { Exception catchException = null; try { throw new Exception("A"); } catch (Exception e) { catchException = e; } finally { Exception exception = new Exception("C"); exception.addSuppressed(catchException); throw exception; } } 复制代码
你能够在抛出一个新异常的时候,使用 initCause
方法,指出这个异常是由哪一个异常致使的,最终造成一条异常链。
详情请查阅公众号以前的关于异常链的文章。
跟以前的“异常覆盖”问题相似, finally
块会覆盖掉 try
和 catch
块的返回值。
因此最佳实践是不要在 finaly
块使用 return
!