Java--异常详解
- 异常分类
- Throwable
- Error
- Exception
- RuntimeExceptionn
- CheckedException
- 异常处理方式
- throw
- throws
- jvm自动抛出
- throw和throws的异同
- 异常的捕获
- 异常调用链
异常分类
如果某个方法不能按照正常的途径完成任务,就可以通过另一种路径退出方法。在这种情况下会抛出一个封装了错误信息的对象。此时,这个方法会立刻退出同时不返回任何值。另外,调用这个方法的其他代码也无法继续执行,异常处理机制会将代码执行交给异常处理器。
Throwable
Throwable 是 Java 语言中所有错误或异常的超类。
也就是说,如果你要抛出或者声明一个异常,那么,这个异常必须继承Throwable.
public class Main {public static void main(String[] args) {
}
public void testMyError() throws MyError{
throw new MyError();
}
public void testMyErrorNon() throws MyErrorNon {
throw new MyErrorNon();
}
public void testMyException() throws MyException {
throw new MyException();
}
public void testMyExceptionNon() throws MyExceptionNon{
throw new MyExceptionNon();
}
}
Error
Error 类是指 java 运行时系统的内部错误和资源耗尽错误。应用程序不会抛出该类对象。如果出现了这样的错误,除了告知用户,剩下的就是尽力使程序安全的终止.
Exception
Exception 又 有 两 个 分 支 , 一 个 是 运 行 时 异 常 RuntimeException , 一个是CheckedException.
RuntimeExceptionn
运行时异常
如 : NullPointerException 、 ClassCastException ; 一 个 是 检 查 异 常CheckedException,如 I/O 错误导致的 IOException、SQLException。 RuntimeException 是那些可能在 Java 虚拟机正常运行期间抛出的异常的超类。 如果出现 RuntimeException,那么一定是程序员的错误.
CheckedException
检查异常
一般是外部错误,这种异常都发生在编译阶段,Java 编译器会强制程序去捕获此类异常,即会出现要求你把这段可能出现异常的程序进行 try catch,该类异常一
般包括几个方面:
异常处理方式
throw
throws
jvm自动抛出
throw和throws的异同
相同点:
不同点:
- 位置不同:
- 功能不同:
2. throws用来声明异常,让调用者知道该方法可能出现的问题,可以给出预先的处理方式;throw抛出具体的异常问题对象,执行到throw,就无法在继续按正常的顺序继续执行了。跳转到调用者,并将具体的问题对象抛给调用者。
3. throws表示出现异常的一种可能性,并不一定会发生这些异常;throw则是抛出了异常,执行throw则一定抛出了某种异常。
异常的捕获
public class Main {public static void main(String[] args) {
try {
testcatch();
} catch (MyException4 myException4) {
myException4.printStackTrace();
} catch (MyException3 myException3) {
myException3.printStackTrace();
} catch (MyException2 myException2) {
myException2.printStackTrace();
} catch (MyException1 myException1) {
myException1.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
public static void testcatch() throws Throwable, MyException4, MyException3, MyException2, MyException1, Exception, Error {
}
}
异常的捕获,最靠近try的,异常范围是最小的。异常匹配时,从上到下进行匹配,如果将范围大的异常捕获放在前面,那么后面比这个异常范围小的异常,永远得不到调用。
异常调用链
每个方法内部出现异常后,要么方法内部使用try捕获异常,方法内解决这个异常。使程序继续运行下去。
要么将异常抛出,由方法的调用者处理这个异常。
public class OMain {public static void main(String[] args) {
try {
new OMain().test4();
} catch (MyException4 myException4) {
myException4.printStackTrace();
}
}
public void test4() throws MyException4 {
try {
test3();
} catch (MyException3 myException3) {
throw new MyException4();
}
}
public void test3() throws MyException3 {
try {
test2();
} catch (MyException2 myException2) {
throw new MyException3();
}
}
public void test2() throws MyException2 {
try {
test1();
} catch (MyException1 myException1) {
throw new MyException2();
}
}
public void test1() throws MyException1 {
try {
test();
} catch (Exception e) {
throw new MyException1();
}
}
public void test() throws Exception {
throw new Exception();
}
}
在这个例子中,真正的异常是在test方法中出现的,但是因为每一个方法都在自己的方法内部捕获了下层异常,然后抛出自己的异常。
此时整个异常已经失真了,从异常堆栈根本无法知道问题出在哪里:
异常调用链的完整,可以帮助我们更快的定位问题。
public class OMain {public static void main(String[] args) {
try {
new OMain().test4();
} catch (MyException4 myException4) {
myException4.printStackTrace();
}
}
public void test4() throws MyException4 {
try {
test3();
} catch (MyException3 myException3) {
MyException4 myException4 = new MyException4();
myException4.initCause(myException3);
throw myException4;
}
}
public void test3() throws MyException3 {
try {
test2();
} catch (MyException2 myException2) {
MyException3 myException3 = new MyException3();
myException3.initCause(myException2);
throw myException3;
}
}
public void test2() throws MyException2 {
try {
test1();
} catch (MyException1 myException1) {
MyException2 myException2 = new MyException2();
myException2.initCause(myException1);
throw myException2;
}
}
public void test1() throws MyException1 {
try {
test();
} catch (Exception e) {
MyException1 myException1 = new MyException1();
myException1.initCause(e);
throw myException1;
}
}
public void test() throws Exception {
throw new Exception();
}
}
在Throwable中,有一个属性cause,默认是自己。这个属性就是记录当前异常的上一个异常是谁。如果cause就是自己,那么,说明这个异常就是最先抛出的异常。
这样整个异常调用链就完整了:
其调用关系,可以完整的从堆栈中看出。
这是最常见的场景。
我们假设这个调用是上下级的调用:
test4->test3->test2->test1->test
其中越是下级,做的工作越简单,也越是底层方法的调用。在底层方法得到调用中,就会出现各种各样的异常(什么异常都有可能)
在例子中出现的是Exception。
在test方法中,可能完全不明白为什么出现异常,或者说,出现异常的原因是什么,如何解决?这些问题在test方法中都无法解决的。
于是test方法将自己无法解决的异常抛出。
在test1方法中,捕获到了异常,可能test1交给test是读取一个文件,结果出现了文件没找到的异常。那么,在test1方法中就知道了,是文件没找到,但是,对于没找到的文件,该如何处理呢?test1就不知道了,于是,继续向上抛出异常,此时异常被进行细化,由文件没找到的异常转换为文件读取失败。
在test2方法中,捕获到了异常,可能test2交给test1是读取一个文件夹的文件,结果出现了部分文件未找到。那么在test2方法中,我们知道了,是部分文件没找到,而不是文件夹内全部的文件没找到。在test2方法中,可以确定,是某一个文件没有找到,那么对于这个文件,我们是要忽略,还是重试呢?test2也不知道,此时只能转换异常,然后继续抛出。转换的异常更加细化,部分文件未找到。
。。。。
以此类推,最终异常被交给了主线程,当主线程也无法确认后,将问题抛给用户,由用户决定。用户决定如何处理。
这个过程可以类比我们使用操作系统复制整个文件夹的过程。
里面是一层层的划分,当出现无法抉择的异常,最终抛给用户决定。
遇到可以抉择的异常,直接处理。(一个文件失败自动重试3次。。)
将异常交由调用者处理,是非常正确的处理方式。\
这种方式是金字塔结构,越是底层的方法,异常范围越大。
==还有一种是倒金字塔结构,越是底层的方法,异常越小。==对于这种结构,无需做任何捕获,直接抛出即可。
public class OMain {public static void main(String[] args) {
try {
new OMain().test();
} catch (Throwable e) {
e.printStackTrace();
}
}
public void test4() throws MyException4 {
throw new MyException4();
}
public void test3() throws MyException3 {
test4();
}
public void test2() throws MyException2 {
test3();
}
public void test1() throws MyException1 {
test2();
}
public void test() throws Throwable {
test1();
}
}