文章目录
Java异常架构与异常关键字
Java 异常架构
Java 异常是Java 提供的一种识别与响应错误的一致性机制。Java 异常可以使程序中异常处理代码和正常业务分离,提高代码的健壮性。
1. Throwable
Throwable 是 Java 中所有错误和异常的超类。
Throwable 包含两个子类:Error(错误)和 Exception(异常),他们通常用于指示发生了异常情况,其中 Error 是非受检异常。
Throwable 提供了 printStackTrace() 等接口用来获取堆栈跟踪数据等信息。
2. Error(错误)
Error 是指程序中无法处理的错误,表示应用程序出现了严重的错误。
此类错误一般表示代码运行时 JVM 出现错误,一旦发生此类错误,JVM 就会停止运行。
3. Exception(异常)
Exception 是程序可以捕获并且可以处理的异常,主要包括有IOException(IO 流异常)、ClassNotFoundException(没有找到指定的异常)、CloneNotSupportedException、RuntimeException。其中,RuntimeException 是运行时异常,属于非受检异常,其他的是编译时异常,属于受检异常。
运行时异常
运行时异常是指在Java编译器中不会检查它,在 JVM 运行期间可能出现的异常。
该类异常一般是由逻辑错误引起的,在编译阶段可以编译通过,在运行时可能会由于逻辑错误发生中断,例如下标越界、空指针等等。
编译时异常
Exception 的子类中除 RuntimeException 之外的所有子类异常。编译时异常会由 Java 编译器在 Java 的编译阶段就被抛出,否则不能通过编译。
在程序中,通常不会自定义该类异常,而是直接使用系统提供的异常的。我们必须手动在代码中添加捕获语句来处理该异常。
4. 受检异常和非受检异常
Java 的异常类还可以根据是否受检分为受检异常和非受检异常。
受检异常
编译器要求必须处理的异常。正常的程序在运行过程中,经常容易出现的、符合预期的异常。一旦发生此类异常,就必须采用某种方式进行处理 —— 要么使用 try-catch 捕获、要么使用方法签名中的 throws 关键字抛出,否则编译不通过。
非受检异常
编译器不会进行检查并且不要求必须处理的异常,也就是说编译器发现不了的异常,在编译期间会正常通过,但是在运行期间就会发生错误。该类异常包括运行时异常和Error。
Java 异常关键字
- try:用于监听。将可能会发生异常的代码块放到 try 语句块中,当 try 语句块中发生异常时,异常就会被抛出,try 中的代码就不会再被执行,而是去执行 catch 中的代码。
- catch:用于捕获异常。catch 用来捕获 try 语句块中抛出的异常。
- finally:finally 表示最后的善后工作,finally 语句块总是会被执行。它主要用于回收在 try 语句块中打开的资源(如数据库连接、网络连接等)。当 try 或 catch 语句块中有 return 或者 throw 语句时,只有当 finally 块执行完成之后,才会回去再次执行 try 或者 catch 语句块中的 return 或 throw 语句,如果 finally 中使用了 return 或 throw 等终止方法的语句,则不会跳回执行,直接停止执行。
- throw:用于在方法中抛出异常。
- throws:用在方法签名中,用于声明该方法可能会抛出的异常。
Java异常处理
常见的异常处理方式
抛出异常
在方法中,程序员可以加上 throw 语句,新建异常对象,手动抛出该异常对象。如果异常对象是受检异常则需要在方法声明时加上该异常的抛出即throws语句,或者在方法体内使用 try-catch 处理该异常,否则编译报错。如果抛出的异常对象是非受检异常,即可以显式捕获该异常,也可以完全不理会该异常。
捕获异常
使用 try - catch 包裹处理异常。回答异常处理的流程。
声明异常
在方法签名中声明异常,其实就是自己不想对异常做任何的处理,告它的调用者,让调用者处理可能出现的异常。发生异常时,JVM 会根据调用栈查看是否有处理该异常的代码块,如果有就处理该异常,如果直到 main 方法还没有找到处理的该异常的代码块,JVM就会自己抛出异常,并终止程序。
自定义异常
可以在实际应用过程根据自己可能会出现异常的场景,自定义异常类再抛出。自定义异常时可以继承Exception 和 RuntimeException,继承前者的异常是受查异常,继承后者的异常类是非受查异常。
继承 Exception 的子类的构造方法有两种:一个是无参构造方法,一个是带有详细描述信息的构造参数。
public class MyException extends Exception {
public MyException(){ }
public MyException(String msg){
super(msg);
}
}
异常处理的流程
- 程序先执行 try 中的代码
- 如果 try 中的代码出现异常,就会结束 try 中代码的执行,而是看是否与 catch 中要捕获的异常匹配
- 如果找到匹配的异常类型,就会执行 catch 中的代码
- 如果没有找到匹配的异常类型,就会将异常向上传递到上层调用者
- 无论是否找到匹配的异常类型,如果有 finally 语句,finally 中的代码都会在该方法结束之前被执行一次
- 如果上层调用者也没有处理该异常类型,就继续向上传递
- 一直到 main 方法中,如果也没有合适的代码处理该异常,就会交给 JVM 处理,此时代码就会异常终止
面试题
Error 和 Exception 的区别是什么?
首先他们两个前者是非受检异常,Exception 是受检异常。
Error 类型的错误通常指的是虚拟机相关的错误,如系统崩溃、堆栈溢出、内存不足等,编译器不会对该类错误进行检测,Java 应用程序也不对这类错误进行捕获。一旦发生此类错误,通常程序会被终止,仅靠应用程序本身是无法恢复的。
Exception 类的错误是可以在应用程序中进行捕获并处理的,通常遇到此类错误,应对其进行抛出或直接处理,使应用程序可以继续正常执行。
运行时异常和一般异常(受检异常)的区别是什么?
运行时异常包括 RuntimeException 类及其子类,表示在 JVM 运行期间可能会出现的异常,包括下标出界、空指针异常等。该类异常在Java编译器不会被检查。
受检异常指的是在 Exception 的子类中除 RuntimeException 之外的类及其子类。Java 编译器会检查受检异常。
区别:是否强制要求调用者处理该异常,如果有强制要求调用者必须处理,那么就是受检异常,否则就是非受检异常。
JVM 是如何处理异常的?(对比异常处理的流程)
在一个方法中如果发生异常,这个方法就会创建一个异常对象,并把该对象交给 JVM,该异常对象包括异常名称、异常描述以及发生异常时应用程序的状态。创建异常对象并交给 JVM 的过程称为抛出异常。在抛出异常的方法之前可能有一系列的方法调用,这一系列的方法调用的有序列表称为调用栈(也就是方法一层一层调用的过程)。
JVM 会顺着调用栈去查找是否有处理该异常的代码,如果有,则调用异常处理的代码,如果 JVM 没有找着处理该异常的代码块,就把该异常对象交给它的默认异常处理器,默认异常处理器打印出异常信息,并终止应用程序。
throw 和 throws 的区别是什么?
throw 和 throws 关键字都可以处理Java异常,前者处理异常的方法是是抛出异常,后者是声明异常。
throw:是在方法内部,跟的异常对象名,用来抛出该异常对象,并且只能抛出代码块中的一种类型异常。
如果抛出的是受检异常,则一定要在方法体内使用 try-catch 捕获该异常,或者在方法名后边声明异常由方法调用者处理该异常;如果抛出的是非受检异常,则可以显式捕获该异常,也可以完全不理会该异常,把异常交给方法调用者处理。
throws:用来在方法名中声明这个方法可能会抛出的所有异常,该方法不对这些异常做处理,而是向上传递,谁调用这个方法就把异常抛出谁。throws 用在方法声明后,跟的是异常类名,可以跟多个异常类名,用逗号隔开就行。
在该方法中发生异常时,JVM 会根据调用栈逐层寻找处理该异常的代码块,如果有,则调用异常处理的代码块,如果没有,则由JVM 使用自己的默认异常处理器,打印出异常信息,并终止程序运行。
final、finally、finalize有什么区别?
- final通常用来修饰类、方法、遍历,被 final 修饰的类不能被继承、修饰方法不能被重写、修饰的变量表示该变量为一个常量不能被重新赋值。
- finally一般作用在try-catch代码块中,在处理异常时,通常将一定要执行的代码放在 finally 代码块中,表示不管是否出现异常,该finally代码块都要被执行,一般用来存放一些关闭资源的代码。
- finalize是 Object 类的一个方法。Java中允许使用 finalize() 方法在垃圾回收将对象从内存中清除之前,做必要的清理工作。
try-catch-finally 中哪个部分可以省略?
catch 可以省略。
更为严格的说法是:try 可以处理运行时异常,try-catch 可以处理运行时异常+普通异常。也就是说如果只用 try 捕获普通异常,编译器是编译不通过的。而处理运行时异常时,编译器并没有硬性规定必须加上 catch,所以 catch 可以省略,加上也无所谓。
如果是普通异常,编译器要加上 catch 捕获以便进一步处理,如果是运行时异常,捕获然后丢弃并且进行 finally 代码块中的扫尾工作,或加上 catch 捕获。
try-catch-finally中,如果try或者catch 中有return,finally 还会执行吗?
会在 try 或者 catch 进行 return 之前进行。
示例1:
public static int getInt() {
int a = 10;
try {
System.out.println(a / 0);
a = 20;
} catch (ArithmeticException e) {
a = 30;
return a;
/*
* return a 在程序执行到这一步的时候,这里不是return a 而是 return 30;这个返回路径就形成了
* 但是呢,它发现后面还有finally,所以继续执行finally的内容,a=40
* 再次回到以前的路径,继续走return 30,形成返回路径之后,这里的a就不是a变量了,而是常量30
*/
} finally {
a = 40;
}
return a;
}
执行结果:30
示例2:
public static int getInt() {
int a = 10;
try {
System.out.println(a / 0);
a = 20;
} catch (ArithmeticException e) {
a = 30;
return a;
} finally {
a = 40;
//如果这样,就又重新形成了一条返回路径,由于只能通过1个return返回,所以这里直接返回40
return a;
}
}
执行结果:40
类 ExampleA 继承 Exception,类 ExampleB 继承ExampleA。
try {
throw new ExampleB("b")
} catch(ExampleA e){
System.out.println("ExampleA");
} catch(Exception e){
System.out.println("Exception");
}
结果输出:ExampleA。异常类型匹配按照就近原则。