学习内容
异常
- 什么是异常:在程序中出现的不正常情况
- 异常有什么作用:增强程序的健壮性1
- 异常的存在形式:异常在Java中是以类的形式存在,所有的异常类都可以创建对象
异常继承结构
- Throwable是Error和Exception的直接父类,可以表示所有的错误和异常都是可抛出的
- 错误一旦发生JVM只会有一个结果,那就是终止程序的运行,退出JVM。
- 对于Exception的直接子类所表示的异常我们称之为编译时异常
- 对于RuntimeException的直接子类所表示的异常我们称之为运行时异常
Error
正常情况下,错误时不会发生的,绝大多数错误会导致程序处于不正常状态,错误是无法被处理的,错误一旦发生JVM只会有一个结果,那就是终止程序的运行,退出JVM。
Exception
编译时异常
编译时异常又被称为受控异常/受检测异常,编译时异常发生概率较高,要求开发人员在编写代码时对异常进行预处理2,不处理编译器会报错,所有的编译时异常的直接父类都是Exception。
运行时异常
运行时异常又被称为非受控异常/非受检测异常,运行时异常发生概率较低,开发人员可以对该异常选择处理也可以选择不处理,所有运行时异常的直接父类都是RuntimeExcepiton。
Throwable常用方法
- public String getMessage() -----> 获取异常简单描述信息
- public void printStackTrace() -----> 打印异常堆栈信息
java后台打印异常堆栈追踪信息时候,采用了异步线程的方式打印
异常的处理
我们对异常的处理一般指的是编译时异常,编译时异发生概率高,要求在编写程序时必须预先进行处理;对于运行时异常,并不强制要求对异常进行处理(可以选择处理,也可以选择不处理),就算不处理程序也能通过编译。
throws关键字
在方法声明的位置使用throws关键字,将异常抛给调用者,调用者可以选择对异常处理(try…catch)也可以选择继续上抛(throws),如果异常一直选择上抛,最终会抛给main方法,main方法抛给调用者JVM,JVM知道异常的发生会终止虚拟机。
此处的IO异常是编译时异常,要求编写代码的时候必须进行预处理,不预先处理编译无法通过,而处理异常有两种方式此处选择使用throws关键字将异常上抛
此处可以看到,method方法将异常抛给调用者main方法,main方法没有对异常进行处理,所以此处同样无法通过编译,如果在main方法中选择将异常上抛,那么会将异常抛给JVM。
此时可以通过编译期,但是在程序运行期间,JVM看到异常的发生最终会终止程序的运行
深入throws
- 使用throws关键字抛出异常时,可以上抛多个,异常类型之间使用","隔开
- 可以抛出一个更宽泛的异常,即抛出父类异常类型(多态)
try…catch语句
在可能会发生异常中位置使用try语句块,同时使用catch语句块进行捕捉。对于下面的代码我们使用try…catch语句对异常进行处理
运行结果如下,此处对异常处理完毕,打印异常信息。
finally子句
- 何时使用:通常finally来完成资源的释放/关闭,因为finally语句块有保障,就算try中
发生异常,finally也会正常执行 - finally语句块中的内容一定会执行,(使用System.exit(0)终止虚拟机除外)
- finally语句块必须和try语句块同时出现,不能单独使用
深入try…catch…finally
深入try…catch…finally
- catch后面的小括号中可以是具体的异常类型,也可以是父类的类型(多态)
- catch后面的小括号可以写多种异常类型,例如:“catch(IOException | NullPointerException e) {}” ----> Java8新特性
- catch可以写多个,建议catch的时候,精确的一个一个的处理,这样有利于程序的调试
- catch写多个的时候,从上到下,必须遵守从小到大
- try中语句出现异常后,后面的语句是无法被执行到的
- try语句块不能单独出现,必须和catch或finally语句块联合使用,反之亦然,
即catch、finally语句块不能单独出现,必须和try语句块联合使用
自定义异常
自定义编译时异常
编写一个Java类,让其继承Exception,并在构造方法中调用父类构造
public class MyException extends Exception {
public MyException(){}
public MyException(String message){
super(message);
}
}
自定义运行时异常
编写一个Java类,让其继承RuntimeException,并在构造方法中调用父类构造
public class MyException extends RuntimeException {
public MyException(){}
public MyException(String message){
super(message);
}
}
异常相关面试题
throw和throws区别
- 使用位置不同:throw是在方法体中使用,throws是在方法定义时使用
- 抛出的内容不同:throw抛出的是异常对象,throws抛出的是异常的类型,可以同时抛出多种类型,类型之间使用“,”隔开
- 使用throw一定会有异常,使用throws可能会存在异常
finally与return同时出现
public class Test {
static int i = 10;
public static void main(String[] args){
System.out.println("returnValue: " + method() + "," + "i: " + i);
}
public static int method() {
try {
return i;
} finally {
i++;
}
}
}
上面代码的运行结果是什么? 划我查看
在Java中有这样的两条规则,(规则不容破坏,必须遵守)
- 方法体中的代码必须自上而下依此执行
- return语句一旦执行方法必须结束。
所以对于上面的代码,为了保证这两条语法规则,编译器的编译结果是这样的
- 首先执行try语句块中的代码,将i的值赋值给一个临时变量j (即int j = i;)
- 然后执行finally语句块中的代码,i++;
- 最后将变量j返回
finally、finalize、final区别
final —> 关键字
1. 被final修饰的类无法被继承
2. 被final修饰的方法无法被重写
3. 被final修饰的变量无法再次赋值
finally —> 关键字
1. finally语句块必须和try语句块联合使用
2. finally中的代码必须执行(在try语句块中使用System.exit(0)除外)
finalize —> 标识符
1. finalize是Object类中的一个实例方法名
2. finalize方法是由GC机制调用,重写此方法在当前实例被GC机制回收时执行(实例的遗嘱)