Java异常深入理解
java异常类层次结构
在 Java 中,所有的异常都有一个共同的祖先 Throwable(可抛出)。Throwable 指定代码中可用异常传播机制通过 Java 应用程序传输的任何问题的共性。 Throwable: 有两个重要的子类:Exception(异常)和 Error(错误),二者都是 Java 异常处理的重要子类,各自都包含大量子类。
- Error(错误):是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。例如,Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时,如Java虚拟机运行错误(Virtual MachineError)、类定义错误(NoClassDefFoundError)等。这些错误是不可查的,因为它们在应用程序的控制和处理能力之 外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在 Java中,错误通过Error的子类描述。
- Exception(异常):是程序本身可以处理的异常。Exception 类有一个重要的子类 RuntimeException。RuntimeException 类及其子类表示“JVM 常用操作”引发的错误。例如,若试图使用空值对象引用、除数为零或数组越界,则分别引发运行时异常(NullPointerException、ArithmeticException)和 ArrayIndexOutOfBoundException。
注意:异常和错误的区别:异常能被程序本身可以处理,错误是无法处理。
异常的分类
通常,Java的异常(包括Exception和Error)分为可查的异常(checked exceptions)和不可查的异常(unchecked exceptions)。
- 可查异常(编译器要求必须处置的异常):正确的程序在运行中,很容易出现的、情理可容的异常状况。可查异常虽然是异常状况,但在一定程度上它的发生是可以预计的,而且一旦发生这种异常状况,就必须采取某种方式进行处理。除了RuntimeException及其子类以外,其他的Exception类及其子类都属于可查异常。这种异常的特点是Java编译器会检查它,也就是说,当程序中可能出现这类异常,要么用try-catch语句捕获它,要么用throws子句声明抛出它,否则编译不会通过。
- 不可查异常(编译器不要求强制处置的异常):包括运行时异常(RuntimeException与其子类)和错误(Error)。 Exception 这种异常分两大类运行时异常和非运行时异常(编译异常)。程序中应当尽可能去处理这些异常。
- 运行时异常:都是RuntimeException类及其子类异常,如NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也能编译通过。我们可以不处理运行时异常,当出现这样的异常时,总是由虚拟机接管。比如:我们从来没有人去处理过NullPointerException异常,它就是运行时异常,并且这种异常还是最常见的异常之一。出现运行时异常后,如果没有捕获并处理这个异常(即没有catch),系统会把异常一直往上层抛,一直到最上层,如果是多线程就由Thread.run()抛出,如果是单线程就被main()抛出。抛出之后,如果是线程,这个线程也就退出了。如果是主程序抛出的异常,那么这整个程序也就退出了。也就是说,你如果不对运行时异常进行处理,那么出现运行时异常之后,要么是线程中止,要么是主程序终止。
- 非运行时异常 (编译异常):是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。
几类异常出现的情况
1. try块中,未发生异常
不触发异常,当然是我们乐于看见的。在这种情况下,如果有 finally 块,它会在 try 块之后运行,catch 块永远也不会被运行。
2. try块中,发生异常
在发生异常时,会首先检查异常类型,是否存在于我们的 catch 块中指定的待捕获异常。如果存在,则这个异常被捕获,对应的 catch 块代码则开始运行,finally 块代码紧随其后。
例如:我们只监听了空指针(NullPointerException),此时如果发生了除数为 0 的崩溃(ArithmeticException),则是不会被处理的。
当触发了我们未捕获的异常时,finally 代码依然会被执行,在执行完毕后,继续将异常“抛出去”。
3. catch 或者 finally 发生异常
catch 代码块和 finally 代码块,也是我们编写的,理论上也是有出错的可能。
那么这两段代码发生异常,会出现什么情况呢?
当在 catch 代码块中发生异常时,此时的表现取决于 finally 代码块中是否存在 return 语句。如果存在,则 finally 代码块的代码执行完毕直接返回,否则会在 finally 代码块执行完毕后,将 catch 代码中新产生的异常,向外抛出去。
而在极端情况下,finally 代码块发生了异常,则此时会中断 finally 代码块的执行,直接将异常向外抛出。
实际代码验证
try之前发生异常
private static void test1() throws Exception {
int i = 1/0; ////// exception ///////////
try {
System.out.println("try");
} catch (Exception e) {
System.out.println("catch");
} finally {
System.out.println("finally");
}
System.out.println("after");
}
\
public static void main(String[] args) {
try {
test1();
} catch (Exception e) {
System.out.println("main-catch");
}finally {
System.out.println("main-finally");
}
System.out.println("main");
}
\
结果: try不执行所以finally中的代码也没有执行
main-catch
main-finally
main
try块之后发生异常
private static void test1() throws Exception {
try {
System.out.println("try");
} catch (Exception e) {
System.out.println("catch");
} finally {
System.out.println("finally");
}
int i = 1/0; ////// exception ///////////
System.out.println("after");
}
public static void main(String[] args) {
try {
test1();
} catch (Exception e) {
System.out.println("main-catch");
}finally {
System.out.println("main-finally");
}
System.out.println("main");
}
结果: 异常被上层main方法捕获
try
finally
main-catch
main-finally
main
try块中发生异常
private static void test1() throws Exception {
\
try {
System.out.println("try before");
int i = 1/0; ////// exception ///////////
System.out.println("try after");
} catch (Exception e) {
System.out.println("catch");
} finally {
System.out.println("finally");
}
System.out.println("after");
}
\
public static void main(String[] args) {
try {
test1();
} catch (Exception e) {
System.out.println("main-catch");
}finally {
System.out.println("main-finally");
}
System.out.println("main");
}
结果: 异常被正确处理不再抛出
try before
catch
finally
after
main-finally
main
catch中出现异常且finally中没有return语句
private static void test1() throws Exception {
try {
int i = 1/0; ////// exception ///////////
System.out.println("try");
} catch (Exception e) {
int i = 1/0; ////// exception ///////////
System.out.println("catch");
} finally {
System.out.println("finally");
}
System.out.println("after");
}
\
public static void main(String[] args) {
try {
test1();
} catch (Exception e) {
System.out.println("main-catch");
}finally {
System.out.println("main-finally");
}
System.out.println("main");
}
\
结果: 异常会继续向上抛出
finally
main-catch
main-finally
main
catch中出现异常且finally中有return语句
private static void test1() throws Exception {
try {
int i = 1/0; ////// exception ///////////
System.out.println("try");
} catch (Exception e) {
int i = 1/0; ////// exception ///////////
System.out.println("catch");
} finally {
System.out.println("finally");
return;
}
}
\
public static void main(String[] args) {
try {
test1();
} catch (Exception e) {
System.out.println("main-catch");
}finally {
System.out.println("main-finally");
}
System.out.println("main");
}
\
结果: 异常被阻断 没有继续抛出。
finally
main-finally
main
finally中出异常
private static void test1() throws Exception {
try {
int i = 1/0;
System.out.println("try");
} catch (Exception e) {
System.out.println("catch");
} finally {
int i = 1/0;
System.out.println("finally");
}
}
\
public static void main(String[] args) {
try {
test1();
} catch (Exception e) {
System.out.println("main-catch");
}finally {
System.out.println("main-finally");
}
System.out.println("main");
}
\
结果: finally中发生异常直接向外抛出
catch
main-catch
main-finally
main