Exception 和 Error区别? 可查异常和不可查异常区别?
Error和Exception都是继承了Throwable 类。
Error是指在正常情况下,不大可能出现的情况,绝大部分的 Error 都会导致程序(比如 JVM 自身)处于非正常的、不可恢复状态。既然是非正常情况,所以不便于也不需要捕获,常见的比OutOfMemoryError 之类,都是 Error 的子类。
Exception 又分为可查(checked)异常和不可查(unchecked)异常。
可查异常在源代码里必须显式地进行捕获处理,这是编译期检查的一部分。
不可查异常就是所谓的运行时异常,类似 NullPointerException、ArrayIndexOutOfBoundsException 之类,通常是可以编码避免的逻辑错误,具体根据需要来判断是否需要捕获,并不会在编译期强制要求。
异常类图
异常的处理
在 Java 应用程序中,异常处理机制为:抛出异常,捕捉异常。
抛出异常:当一个方法出现错误引发异常时,方法创建异常对象并交付运行时系统,异常对象中包含了异常类型和异常出现时的程序状态等异常信息。运行时系统负责寻找处置异常的代码并执行。
捕获异常:在方法抛出异常之后,运行时系统将转为寻找合适的异常处理器(exception handler)。潜在的异常处理器是异常发生时依次存留在调用栈中的方法的集合。当异常处理器所能处理的异常类型与方法抛出的异常类型相符时,即为合适 的异常处理器。运行时系统从发生异常的方法开始,依次回查调用栈中的方法,直至找到含有合适异常处理器的方法并执行。当运行时系统遍历调用栈而未找到合适 的异常处理器,则运行时系统终止。同时,意味着Java程序的终止。
处理过程:产生异常状态时,如果当前的context不具备处理当前异常的能力,将在heap 上new
出来一个异常对象,停止当前执行路线,把产生的对象抛给更高层的context。
从方法中抛出的任何异常都必须使用throws子句。
捕捉异常通过try-catch语句或者try-catch-finally语句实现。
总体来说,Java规定:对于可查异常必须捕捉、或者声明抛出。允许忽略不可查的RuntimeException和Error。
Error 也是可以catch的
如果catch的异常在前面已经 有catch了 后面再catch 编译不通过。编辑器会提示你。
异常处理的两个基本原则
1、尽量不要捕获类似 Exception 这样的通用异常,而是应该捕获特定异常
这是因为在日常的开发和合作中,我们读代码的机会往往超过写代码,软件工程是门协作的艺术,
所以我们有义务让自己的代码能够直观地体现出尽量多的信息,而泛泛的 Exception 之类,
恰恰隐藏了我们的目的。另外,我们也要保证程序不会捕获到我们不希望捕获的异常。
比如,你可能更希望 RuntimeException 被扩散出来,而不是被捕获。进一步讲,除非深思熟虑了,
否则不要捕获 Throwable 或者 Error,这样很难保证我们能够正确程序处理 OutOfMemoryError。2、不要生吞(swallow)异常
这是异常处理中要特别注意的事情,因为很可能会导致非常难以诊断的诡异情况。
如果我们不把异常抛出来,或者也没有输出到日志(Logger)之类,程序可能在后续代码以不可控的方式结束。没人能够轻易判断究竟是哪里抛出了异常,以及是什么原因产生了异常。
try-catch-finally处理异常
try 块:用于捕获异常。其后可接零个或多个catch块,如果没有catch块,则必须跟一个finally块。
catch 块:用于处理try捕获到的异常。
finally 块:无论是否捕获或处理异常,finally块里的语句都会被执行。当在try块或catch块中遇到return语句时,finally语句块将在方法返回之前被执行。在以下4种特殊情况下,finally块不会被执行:
- 在finally语句块中发生了异常。
- 在前面的代码中用了System.exit()退出程序。
- 程序所在的线程死亡。
- 关闭CPU。
try、catch、finally语句块的执行顺序:
当try没有捕获到异常时:try语句块中的语句逐一被执行,程序将跳过catch语句块,执行finally语句块和其后的语句;
当try捕获到异常,catch语句块里没有处理此异常的情况:当try语句块里的某条语句出现异常时,而没有处理此异常的catch语句块时,此异常将会抛给JVM处理,finally语句块里的语句还是会被执行,但finally语句块后的语句不会被执行;
当try捕获到异常,catch语句块里有处理此异常的情况:在try语句块中是按照顺序来执行的,当执行到某一条语句出现异常时,程序将跳到catch语句块,并与catch语句块逐一匹配,找到与之对应的处理程序,其他的catch语句块将不会被执行,而try语句块中,出现异常之后的语句也不会被执行,catch语句块执行完后,执行finally语句块里的语句,最后执行finally语句块后的语句;
什么时候用到finally?
某些事务(除内存外)在异常处理完后需要恢复到原始状态, 如:开启的文件,网络的连接等。
Throw 和 Throws
throw语句用在方法体内,表示抛出异常,由方法体内的语句处理。
throws语句用在方法声明后面,表示再抛出异常,由该方法的调用者来处理。
throws主要是声明这个方法会抛出这种类型的异常,使它的调用者知道要捕获这个异常。
throw是具体向外抛异常的动作,所以它是抛出一个异常实例。
throws说明你有那个可能,倾向。
throw的话,那就是你把那个倾向变成真实的了。
throws出现在方法函数头;
而throw出现在函数体。
throws表示出现异常的一种可能性,并不一定会发生这些异常;
throw则是抛出了异常,执行throw则一定抛出了某种异常。
两者都是消极处理异常的方式(这里的消极并不是说这种方式不好),只是抛出或者可能抛出异常,但是不会由函数去处理异常,真正的处理异常由函数的上层调用处理。
// 例1:throw 和 throws
public class ThrowEx {
public void testThrowEx() {
try {
int a = 3 / 0;
} catch (ArithmeticException e) {
throw new MyException("除0异常");
}
}
public void testThrowsEx() throws ArithmeticException {
int a = 3 / 0;
}
}
自定义异常
使用Java内置的异常类可以描述在编程时出现的大部分异常情况。除此之外,用户还可以自定义异常。用户自定义异常类,只需继承Exception类RuntimeException即可。
在程序中使用自定义异常类,大体可分为以下几个步骤:
(1)创建自定义异常类。
(2)在方法中通过throw关键字抛出异常对象。
(3)如果在当前抛出异常的方法中处理异常,可以使用try-catch语句捕获并处理;否则在方法的声明处通过throws关键字指明要抛出给方法调用者的异常,继续进行下一步操作。
(4)在出现异常方法的调用者中捕获并处理异常。
// 例2:自定义异常
public class MyException extends RuntimeException {
private String myExceptionMsg;
public MyException() {
myExceptionMsg = "";
}
public MyException(String myExceptionMsg) {
this.myExceptionMsg = myExceptionMsg;
}
public String getMyExceptionMsg() {
return myExceptionMsg;
}
public void setMyExceptionMsg(String myExceptionMsg) {
this.myExceptionMsg = myExceptionMsg;
}
}
经典问题
- NoClassDefFoundError 和 ClassNotFoundException 有什么区别?
答:NoClassDefFoundError是一个错误(Error),而ClassNOtFoundException是一个异常。
ClassNotFoundException是在编码的时候编译器就能告诉你这个地方需要捕获异常,
如:你使用Class.forName的时候就必须要你捕获或者throws这个异常。
而NoClassDefFoundError在Javac已经把程序成功的编译成字节码文件了,当JVM进程启动,
通过类加载器加载字节码文件,在classpath下找不到对应的类进行加载时就会发生NoClassDefFoundError这个错误。