java异常简介
Error
Error用来表示编译时和系统错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。例如:系统崩溃,虚拟机错误,内存空间不足,方法调用栈溢出等。如java.lang.StackOverFlowError和Java.lang.OutOfMemoryError。对于这类错误,Java编译器不去检查他们。对于这类错误的导致的应用程序中断,仅靠程序本身无法恢复和预防,遇到这样的错误,建议让程序终止。
Exception
Exception(异常)是应用程序中可能的可预测、可恢复问题。异常一般是在特定环境下产生的,通常出现在代码的特定方法和操作中。遇到这类异常,应该尽可能处理异常,使程序恢复运行,而不应该随意终止异常。
Exception又分为运行时异常(Runtime Exception)和受检查的异常(Checked Exception )。 RuntimeException:Java编译器不去检查它,也就是说,当程序中可能出现这类异常时,即使没有用try……catch捕获,也没有用throws抛出,还是会编译通过,如除数为零的ArithmeticException、错误的类型转换、数组越界访问和试图访问空指针等。Checked Exception:这类异常要么try...catch捕获处理,要么用throws字句声明抛出,交给它的调用方处理,否则编译不会通过。try-catch-finally-return执行顺序:
1、不管是否有异常产生,finally块中代码都会执行,即便在try或catch中加入了continue、break或者return。
2、finally是在return后面的表达式运算后执行的,所以函数返回值是在finally执行前确定的。无论finally中的代码怎么样,返回的值都不会改变,仍然是之前return语句中保存的值; 例如:public static void main(String[] args) { System.out.println(Main.test());; } public static int test() { int x = 1; try { x += 2; return x; } finally { ++x; } }复制代码
程序返回值:3
3、finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值。
public static void main(String[] args) { System.out.println(Main.test());; } public static int test() { int x = 1; try { x += 2; return x; } finally { ++x; return x; } }复制代码
程序返回值:4
异常信息缺失情况
重新抛出异常
有时我们在捕获到异常后,可能在捕获的地方不适合处理该异常,我们需要将它重新抛出:
catch(Exception e){ throw e; } 复制代码
这样我们可以将异常交给上一级环境处理,但是这样抛出的异常携带的信息,也就是printStackTrace()方法显示的是原来异常抛出点的调用栈信息,而非重新抛出点的信息,这样重新抛出点的调用信息就被掩盖了。如果想更新重新抛出点信息到这个异常调用栈中,可以使用fillInStackTrace()方法,那么当前调用栈的信息就更新到了这个异常对象中了:
catch(Exception e){ throw e.fillInStackTrace(); }复制代码
还有一种情况,也会存在类似的丢失现象:
catch(Exception e){ throw new Exception();} 复制代码
这样我们上一级的抛出的异常信息就丢了,接收异常的地方就是只能得到new Exception()这个异常的信息。在JDK1.4以前如果你希望保存丢失的那个异常信息,只能通过编码的方式自己实现,而在JDK1.4后,Throwable类添加了一个Throwable类型的属性cause,用来表示原始异常,那么我们就可以通过异常链从新的异常追踪到异常最初发生的位置。我们可以通过构造函数或initCause(Throwable cause)方法传入一个Throwable对象用来记录原始异常。
Throwable.java:
/** * The throwable that caused this throwable to get thrown, or null if this * throwable was not caused by another throwable, or if the causative * throwable is unknown. If this field is equal to this throwable itself, * it indicates that the cause of this throwable has not yet been * initialized. * * @serial * @since 1.4 */ private Throwable cause = this;复制代码
异常链
另外,finally语句也可能会造成异常信息丢失:
class SomeException extends Exception { @Override public String toString() { return "Some exception"; }}class OtherException extends Exception { @Override public String toString() { return "Other exception"; }}public class Main { void some() throws SomeException { throw new SomeException(); } void other() throws OtherException { throw new OtherException(); } public static void main(String[] args) { try { Main test = new Main(); try { test.some(); } finally { test.other(); } } catch (Exception e) { e.printStackTrace(); } }}复制代码
程序返回值:
Other exception at Main.other(Main.java:23) at Main.main(Main.java:32)复制代码
把最外一层try看作是上一级程序的处理,在这个try里面发生了两次异常,但是我们只能获得从finally中抛出的异常信息,而在some()方法中的异常信息丢失,这种情况我们称上一个异常被抑制了。即finally中抛出的异常会抑制其对应的try或catch中抛出的异常。
在JDK1.7之后,Throwable添加了一个属性suppressedExceptions,用来表示被抑制的异常。我们可以使用addSuppressed(Throwable exception)和getSuppressed()方法解决此问题public static void main(String[] args) { try { Main test = new Main(); Exception exception = null; try { test.some(); } catch (SomeException e) { exception = e; throw e; } finally { if (exception != null) { try { test.other(); } catch (OtherException e) { exception.addSuppressed(e); } } else { test.other(); } } } catch (Exception e) { e.printStackTrace(); } }复制代码
程序返回值:
Some exception at Main.some(Main.java:19) at Main.main(Main.java:31) Suppressed: Other exception at Main.other(Main.java:23) at Main.main(Main.java:38)复制代码
栈轨迹
捕获到异常时,往往需要进行一些处理。比较简单直接的方式就是打印异常栈轨迹Stack Trace。
通过查看源码Throwable.java中printStackTrace()方法,我们可以对上述异常信息丢失的解决办法有更加清晰地认识:private void printStackTrace(PrintStreamOrWriter s) { // Guard against malicious overrides of Throwable.equals by // using a Set with identity equality semantics. SetdejaVu = Collections.newSetFromMap(new IdentityHashMap ()); dejaVu.add(this); synchronized (s.lock()) { // Print our stack trace s.println(this); StackTraceElement[] trace = getOurStackTrace(); for (StackTraceElement traceElement : trace) s.println("\tat " + traceElement); // Print suppressed exceptions, if any for (Throwable se : getSuppressed()) se.printEnclosedStackTrace(s, trace, SUPPRESSED_CAPTION, "\t", dejaVu); // Print cause, if any Throwable ourCause = getCause(); if (ourCause != null) ourCause.printEnclosedStackTrace(s, trace, CAUSE_CAPTION, "", dejaVu); } }复制代码
从源码中我们可以发现,在打印异常栈轨迹时,打印顺序为:异常本身栈轨迹信息、被抑制异常栈轨迹信息、原始异常栈轨迹信息。
try-with-resources 语法糖
在JDK 7之前,资源需要我们手动关闭。如:
String s = "Some String"; BufferedWriter writer = null; try { writer = new BufferedWriter(new FileWriter("test")); writer.write(s, 0, s.length()); } catch (IOException x) { System.err.format("IOException: %s%n", x); } finally { if (writer != null) { try { writer.close(); } catch (IOException e) { e.printStackTrace(); } } }复制代码
try-with-resources 是 JDK 7 中一个新的异常处理机制,它能够很容易地关闭在 try-catch 语句块中使用的资源。所谓的资源(resource)是指在程序完成后,必须关闭的对象。try-with-resources 语句确保了每个资源在语句结束时关闭。所有实现了 java.lang.AutoCloseable 接口(其中,它包括实现了 java.io.Closeable 的所有对象),可以使用作为资源。
在 try 语句中越是最后使用的资源,越是最早被关闭。
class Resource implements AutoCloseable { void doSome() { System.out.println("do something"); } @Override public void close() throws Exception { System.out.println("resource is closed"); }}public class Main { public static void main(String[] args) { try(Resource res = new Resource()) { res.doSome(); } catch(Exception ex) { ex.printStackTrace(); } }}复制代码
程序返回值:
do somethingresource is closed复制代码
try-with-resources 声明在 JDK 9 已得到改进。如果你已经有一个资源是 final 或等效于 final 变量,您可以在 try-with-resources 语句中使用该变量,而无需在 try-with-resources 语句中声明一个新变量。
try-with-resources 是语法糖,那么背后又是如何实现的呢?
下面代码:try(FileInputStream fstream = new FileInputStream("test")) { fstream.read(); } catch (IOException e) { e.printStackTrace(); }复制代码
编译后反编译为:
try { FileInputStream fstream = new FileInputStream("test"); Throwable var2 = null; try { fstream.read(); } catch (Throwable var12) { var2 = var12; throw var12; } finally { if (fstream != null) { if (var2 != null) { try { fstream.close(); } catch (Throwable var11) { var2.addSuppressed(var11); } } else { fstream.close(); } } } } catch (IOException var14) { var14.printStackTrace(); }复制代码
从反编译代码可以发现,其背后也是使用了 addSuppressed(Throwable exception) 方法来实现对异常信息的处理