Java异常是Java提供的一种识别及响应错误的一致性的机制。Java异常体制可以使程序中异常处理代码和正常业务代码分离,保证程序代码更加优雅,并能够提高程序的健壮性。
Java异常体系结构
Throwable
Throwable类是所有异常和错误的超类,两个直接子类为Error和Exception。
Throwable包含了其线程创建时线程执行堆栈的快照,它提供了printStackTrace()等接口用于获取堆栈跟踪数据等信息。
Error
Error类及其子类,程序中无法处理的错误,表示应用程序中出现了严重的错误。一般是指与虚拟机相关的问题,如系统崩溃,虚拟机错误,内存空间不总、方法调用栈溢出等。对于这类错误导致的应用程序中断,仅靠程序本身无法恢复和预防,遇到这样的错误,建议让程序终止。
它是由JVM产生和抛出的,通常有 Virtual MachineError(虚拟机运行错误)、NoClassDefFoundError(类定义错误)等。 比如 OutOfMemoryError:内存不足错误StackOverflowError:栈溢出错误。此类错误发生时,JVM 将终止线程。
Exception
Exception是程序本身可以处理的异常,这种异常分两大类:运行时异常和编译时异常。程序中应当尽可能去处理这些异常。
运行时异常
RuntimeException类及其子类属于运行时异常,表示在程序运行期间可能会出现的异常。
比如NullPointerException空指针异常、ArrayIndexOutBoundException数组 下标越界异常ClassCastException类型转换异常、ArithmeticExecption算术异常。
这些异常是不受查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。
RuntimeException异常会由Java虚拟机自动抛出并自动捕获。
编译时异常
Exception中除了RuntimeException及其子类之外的异常。从程序语法角度讲是必须进行处理的异常。
Java编译器会检查它。如果程序中出现此类异常,要么通过throws进行声明抛出,要么通过try-catch进行捕获处理,否则不能通过编译。
如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。
该异常我们必须手动在代码里添加捕获语句来处理该异常。
受查异常和非受查异常
Java中所有的异常可以分为受查异常和非受查异常。
受查异常:编译器要求必须处理的异常。除 RuntimeException 及其子类外,其他的 Exception 异常都属于受查异常。
编译器会检查此类异常,也就是说当编译器检查到应用中的某处可能会此类异常时,将会提示你处理本异常要么使用trycatch捕获,要么使用方法签名中用 throws 关键字抛出,否则编译不通过。
非受查异常:编译器不hi进行检查并且不要求必须处理的异常,程序中出现此类异常,即使没有try-catch捕获,也没有使用throws抛出该异常,编译也会正常通过。
包含运行时异常(RuntimeException及其子类)和错误(Error)
Java异常关键字
- try:用于监听。当try语句块内发生异常时,异常就被抛出。
- catch:用来捕获异常。捕获try语句块中发生的异常。
- finally:finally语句块总是会被执行。
- throw:用于抛出异常
- throws:用来声明该方法可能抛出的异常,用在方法签名上。
Java异常处理
throws直接抛出异常
通常,应该捕获那些知道如何处理的异常,将不知道如何处理的异常继续传递下去。传递异常可以在方法签名处使用 throws 关键字声明可能会抛出的异常。
private static void readFile(String filePath) throws IOException {
File file = new File(filePath);
String result;
BufferedReader reader = new BufferedReader(new FileReader(file));
while((result = reader.readLine())!=null) {
System.out.println(result);
}
reader.close();}
封装异常再抛出
有时我们会从 catch 中抛出一个异常,目的是为了改变异常的类型。多用于在多系统集成时,当某个子系统故障,异常类型可能有多种,可以用统一的异常类型向外暴露,不需暴露太多内部异常细节。
private static void readFile(String filePath) throws MyException {
try {
// code
} catch (IOException e) {
MyException ex = new MyException("read file failed.");
ex.initCause(e);
throw ex;
}
}
try-catch捕获异常
在一个 try-catch
语句块中可以捕获多个异常类型,并对不同类型的异常做出不同的处理。
private static void readFile(String filePath) {
try {
//可能会发生的异常
} catch (FileNotFoundException e) {
//针对FileNotFoundException异常进行处理
} catch (IOException e){
//针对IOException进行处理
}
}
try-catch-finally
当方法中发生异常,异常处之后的代码不会再执行,如果之前获取了一些本地资源需要释放,则需要在方法正常结束时和 catch 语句中都调用释放本地资源的代码,显得代码比较繁琐,finally 语句可以解决这个问题。
public class ExceptionTest {
public static void main(String[] args) {
try {
// 抛出(throw)异常
System.out.println("执行try语句块,抛出异常");
throw new Error();
} catch (Exception e) {
System.out.println("执行catch语句块,已捕捉到Exception异常");
e.printStackTrace();
} catch (Error e) {
System.out.println("执行catch语句块,已捕捉到Error错误");
e.printStackTrace();
} finally {
System.out.println("执行finally语句块");
}
}
}
注意:
- catch不能独立于 try 存在。
- catch里面不能没有内容
- 在 try/catch 后面添加 finally 块并非强制性要求的。
- try 代码后不能既没 catch 块也没 finally 块。
- try里面越少越好。
- try, catch, finally 块之间不能添加任何代码。
- finally里面的代码最终一定会执行(除了JVM退出)
- 如果程序可能存在多个异常,需要多个catch进行捕获。
- 多个catch块时候,最多只会匹配其中一个异常类且只会执行该catch块代码,而不会再执行其它的catch块,且匹配catch语句的顺序为从上到下,也可能所有的catch都没执行。
finally的作用:
- 无论try所指定的程序块中是否抛出异常,也无论catch语句的异常类型是否与所抛弃的异常的类型一致,finally中的代码一定会被执行到。
- finally语句为异常处理提供一个统一的出口,使得在控制流程转到程序的其他部分之前,能够对程序的状态作统一的管理。
- 通常在finally语句中可以进行资源的清除工作,如关闭打开文件、删除临时文件等。
finally注意事项
finally执行的实际是在方法返回之前(try或者catch中如果有return 会在这个return 之前执行finally)。
但是如果finally中也存在return语句,那么就会执行finally中的return,从而不会执行到try中原有的return。
一般不建议再finally中写return(会被编译器当作一个警告)
自定义异常
自定义异常通常会继承自Exception或者RuntimeException。继承及Exception的默认是受查异常,继承自RuntimeException的异常默认是非受查异常。
自定义异常应该总是包含如下的构造函数:
- 一个无参的构造函数
- 一个带有String参数的构造函数,并传递给父类的构造函数
- 一个带有String参数和Throwable参数的构造函数,并都传给父类构造函数
- 一个带有Throwable参数的构造函数,并传递给父类的构造函数
//自定义异常类OverdraftException
public class OverdraftException extends Exception{
private double deficit;
public double getDeficit() {
return deficit;
}
public OverdraftException() {
}
public OverdraftException(String message, double deficit) {
super(message);
this.deficit = deficit;
}
}
Java异常常见面试题
error和Exception的区别?
- Error类和Exception类的父类都是Throwable类。
- Error 类一般是指与虚拟机相关的问题,如系统崩溃,虚拟机错误,内存空间不足,方法调用栈溢出等。对于这类 错误的导致的应用程序中断,仅靠程序本身无法恢复和和预防,遇到这样的错误,建议让程序终止。
- Exception 类表示程序可以处理的异常,可以捕获且可能恢复。遇到这类异常,应该尽可能处理异常,使程序恢复 运行,而不应该随意终止异常。
- Exception 类又分为运行时异常(Runtime Exception)和受检查的异常(Checked Exception ),运行时异常;ArithmaticException,IllegalArgumentException,编译能通过,但是一运行就终止了,程序不会处理运行时异常,出现这类异常,程序会终止。而受检查的异常,要么用 try。。。catch 捕获,要么用 throws 字句声明抛出,交给它 的父类处理,否则编译不会通过。
throw和throws的区别?
throw:
- throw语句用在方法体内,表示抛出异常,由方法体内的语句处理
- throw是具体向外抛出异常的动作,所以它抛出的是一个异常实例,执行throw一定是抛出了某种异常。
throws:
- throws语句是用在方法声明后面,表示如果抛出异常,由该方法的调用者来进行异常的处理
- throws主要是声明这个方法会抛出某种类型的异常,让它的使用者要知道需要捕获的异常的类型
- throws表示出现异常的以后在哪个可能性,并不一定会发生这种异常。
final、finally、finalize的区别?
- final: 可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、修饰变量表示该变量是一个常量不能被重新赋值。
- finally: 一般作用在try-catch代码块中,在处理异常的时候,通常将一定要执行的代码放在finally代码块中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。
- finalize: Object类的一个方法,在垃圾回收器执行的时候会调用被回收对象的该方法,可以覆盖此方法提供垃圾收集时的其他资源回收,例如关闭文件等。该方法更像是一个对象声明周期的临终方法,当该方法被系统调用则代表该对象即将“死亡”。我们主动行为上调用该方法并不会导致该对象“死亡”,这是一个被动的方法(其实就是回调方法),不需要我们调用。
常见的RuntimeException有哪些?
-
java.lang.NullPointerException 空指针异常;出现原因:调用了未经初始化的对象或者是不存在的对象。
-
java.lang.ClassNotFoundException 指定的类找不到;出现原因:类的名称和路径加载错误;通常都是程序 试图通过字符串来加载某个类时可能引发异常。
-
java.lang.NumberFormatException 字符串转换为数字异常;出现原因:字符型数据中包含非数字型字符。
-
java.lang.IndexOutOfBoundsException 数组下标越界异常,常见于操作数组对象时发生。
-
java.lang.IllegalArgumentException 方法传递参数错误。
-
java.lang.ClassCastException 数据类型转换异常。
-
java.lang.NoClassDefFoundException 未找到类定义错误。
-
SQLException SQL 异常,常见于操作数据库时的 SQL 语句错误。
-
java.lang.InstantiationException 实例化异常。
-
java.lang.NoSuchMethodException 方法不存在异常。