Java中的异常机制
异常的类型
异常类型的本质依然是类的对象
异常也是一个类,都继承自Exception
类;
异常类型支持提前报错
运行时异常
在编译中无法感知代码是否会出现问题,只有在运行时才知道出错。异常也是由类定义的,所有运行时异常都继承自RuntimeException
类
例
public static void main(String[] args) {
Object object = null;
object.toString(); //这种情况就会出现运行时异常
}
报错
编译时异常
指出可能出现的异常,在编译阶段需要进行处理(捕获异常)必须要考虑到出现异常的情况,如果不进行处理,将无法通过编译。
默认继承自Exception
类的异常时都是编译时异常
错误
异常不一定会导致致命的问题,但是错误会
比如OutOfMemoryError
就是内存溢出错误;无限递归
自定义异常
异常分为两类,编译时异常和运行时异常
编译时异常
public class TestException extends Exception{
public TestException(String message) {
super(message); //选择使用父类的带参构造,这个参数就是异常的原因
}
}
编译时异常只需要继承Exception就行了,因为子类巨多
运行时异常
运行时异常只需要继承RuntimeException
就行了
public class TestException extends RuntimeException{
public TestException(String message){
super(message);
}
}
由此可见 Exception继承自Throwable,RuntimeException继承自Exception
运行时异常同同样也有很多,只不过运行时异常和编译型异常在使用时有一些不同,我们会在后面的学习中慢慢认识。
当然还有一种类型是Error,它是所有错误的父类,同样是继承自Throwable的。
抛出异常
当别人调用我们的方法,传入错误的参数导致程序无法运行,这时就可以手动抛出一个异常来终止程序继续运行下去,同时告知上一级方法执行出现了问题
public static int test(int a,int b) {
if(b==0) {
throw new RuntimeException("被除数不能为0");
}
return a/b;
}
异常的抛出同样需要创建一个异常对象出来,抛出异常实际上是将这个异常对象抛出。异常对象携带了抛出异常时的一些信息,比如因为什么原因导致的异常,在RuntimeError
的构造方法中写入原因
当出现异常时
程序会终止,并且打印栈追踪信息,这里的两个at
就是在告知程序运行到哪里出错,位于最上面的就是发生异常的最核心位置。
并且这里会打印出当前抛出的异常类型和我们刚刚自定义异常信息
非运行时异常
如果在方法中抛出非运行时异常,那么必须告知函数的调用方法会抛出某个异常,函数调用方必须要对抛出的这个异常进行相应的处理
private static void test() throws Exception { //使用throws关键字告知调用方此方法会抛出哪些异常,请调用方处理好
throw new Exception("我是编译时异常!");
}
如果不同分支出现不同的异常,那么所有方法中可能会抛出的异常都需要注明
private static void test(int a) throws FileNotFoundException, ClassNotFoundException { //多个异常使用逗号隔开
if(a == 1)
throw new FileNotFoundException();
else
throw new ClassNotFoundException();
}
并不是只有非运行时异常可以明确指出,运行时异常也可以,但是不强制要求
private static void test(int a) throws RuntimeException {
throw new RuntimeException();
}
父类异常情况下子类
在重写方法时,如果父类中的方法表明了会抛出某个异常,只要重写内容中不会抛出对应的异常我们就可以省略
@Override
protected Object clone() {
return new Object();
}
异常的处理
当程序没有按照我们预想的结果运行而出现异常时,为了让程序继续运行下去,就需要对异常进行捕获
try-catch
如果某个方法明确指出抛出哪些异常,除非抛出的异常是一个运行时异常,否则我们必须要使用try-catch
语句进行异常的捕获,不然无法通过编译
我们可以将代码编写到try
语句中,只要是在这个范围内发生的异常,都可以被捕获,使用catch
关键字对指定的异常进行捕获
public static void main(String[] args) {
try{
Object object = null;
object.toString();
}catch(NullPointerException e) { //异常本身也是一个对象,所以是利用一个局部变量接收异常
}
System.out.println("程序正常运行!");
}
之后可以看到当我们捕获异常,程序可以继续正常运行,并不会像之前一样直接结束。
catch
的捕获类型只能是Throwable
的子类,也就是说要么是抛出的异常,要么就是错误,不能是其他的子类
我们也可以在catch
语句块中对捕获的异常进行处理
public static void main(String[] args) {
try {
Object object = null;
object.toString();
} catch (NullPointerException e) {
e.printStackTrace();
System.out.println("异常消息"+e.getMessage());
}
System.out.println("程序继续正常运行");
}
如果某个方法明确指出会抛出哪些异常,除非抛出的异常是一个运行时异常,否则我们必须要使用try-catch语句块进行异常的捕获,不然就无法通过编译:
public static void main(String[] args) {
test(10); //会出现异常报错
}
private static void test(int a) throws IOException {
throw new IOException();
}
如果我们不想在当前方法处理。那就继续throw
注意,如果已经是主方法了,那么就相当于到顶层了,此时发生异常再往上抛出的话,就会直接交给JVM进行处理,默认会让整个程序终止并打印栈追踪信息
public static void main(String[] args) throws IOException {
test(10);
}
private static void test(int a) throws IOException {
throw new IOException();
}
如果需要捕获的异常是个异常的父类,那么当发生这个异常,同样可以捕获到
public static void main(String[] args) throws IOException {
try {
int[] arr = new int[1];
arr[1] = 100; //这里发生的是数组越界异常,它是运行时异常的子类
} catch (RuntimeException e){ //使用运行时异常同样可以捕获到
System.out.println("捕获到异常");
}
}
多重异常捕获
但是要注意顺序,父类在前,会将子类的也捕获,之后的错误就永远都不会捕获了
try {
//....
} catch (NullPointerException e) {
} catch (IndexOutOfBoundsException e){
} catch (RuntimeException e){
}
简写
try {
//....
} catch (NullPointerException | IndexOutOfBoundsException e) { //用|隔开每种类型即可
}
当我们希望,程序运行时,无论是否出现异常,都会在最后执行任务,可以交给finally
语句块来处理:
try {
//....
}catch (Exception e){
}finally {
System.out.println("lbwnb"); //无论是否出现异常,都会在最后执行
}
**try语句至少要配合catch
或者finally
中的一个
断言表达式
我们可以使用断言表达式来对某些东西进行判断,如果判断失败会抛出错误,只不过默认情况下没有开启断言,我们需要在虚拟机参数中手动开启
需要使用到assert
关键字,如果判断结果为false,则会出现AssertionError错误