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.
Set<Throwable> dejaVu =
Collections.newSetFromMap(new IdentityHashMap<Throwable, Boolean>());
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 something
resource 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) 方法来实现对异常信息的处理