所谓的异常就是不正常,我们在之前很多地方有碰到异常,比如对象向下转型时有碰到ClassCastException。所有的异常都继承自Exception类。Exception下面有一个重要的的子类叫做RuntimeException。
异常分为编译期异常和运行时异常,如Exception就是编译期异常,而RuntimeException是运行时异常,如果在方法中抛出了一个编译期异常,那么必须处理该异常,继续向上抛出或者就地处理,而对于运行异常可以选择不处理,那么就会默认交给JVM处理。
处理异常的过程
首先JVM检测到了异常,这时JVM会创建一个异常对象,该对象包含发生错误的内容、原因和位置。如果在发生异常处的方法内没有异常处理逻辑,那么JVM会把该异常抛给这个方法的调用者,如果该调用者也没有异常处理逻辑,那么就会一直向上抛出,直到遇到main方法,如果main方法也没有异常处理逻辑,那么这时异常就会抛给JVM,JVM会打印红色字体至控制台,并且终止Java程序的运行。
与异常有关的关键字有五个,分别为throw, throws, try, catch, finally。下面介绍这五个关键字的作用。
throw
throw关键字用于在方法中抛出一个异常,如下
public static double divide(double a, double b) { |
这个方法的作用是a/b,在检测到b=0时,我们抛出了一个异常,并给出提示信息”除0”。现在我们在main方法中调用该方法
System.out.println(divide(5,0)); |
由于我们没有对异常进行处理,所以程序会进行终结并在控制台打印出信息:
注意:
throw
必须写在方法的内部new
的必须是Exception
或其子类
throws
throws的作用是声明异常,或者说叫把异常抛给调用者,我们知道,编译期异常是必须要进行处理,那么处理的办法有两种,一种就是把继续向上抛出,另一种就是使用try-catch进行处理。而throws就是将异常继续抛出,现在我们将上面的ArithmeticException改为Exception,由于Exception为编译期异常,必须进行处理,否则编译不通过
由图片可以知道,由于我们没有对该异常处理,所以编译没有通过,我们这里的处理办法就是继续抛出
我们通过throws关键字抛出异常了,但是我们发现在main方法处又出现了问题,这是因为main方法没有处理divide()方法可能抛出的异常,因为divide()抛出的异常为编译期异常,必须进行处理,这里我们继续向上抛出,如下
这时代码就不会报错了。
注意:
- 使用
throws
时可以抛出多个异常,异常与异常之间使用逗号隔开 - 如果抛出的多个异常具有父子类关系,那么抛出父类异常就可以了
try-catch
虽然我们通过throws可以向上抛出异常,但是如果不进行处理的话,最终还是会抛出给JVM,而JVM的处理方法就是打印异常信息然后终止程序。我们应该在异常发生时进行捕获,这个时候就不会将异常最终抛给JVM,程序就不会终止,而是会继续的执行下去。进行捕获的代码就是try-catch。具体格式为
try { |
所以我们重写上面的那个方法为
public class TestException { |
运行结果为
java.lang.ArithmeticException: 除0 |
可见后面的代码执行了。
Throwable(它是Exception的父类)中有三个处理异常的方法
- getMessage
- 打印简短信息
- toString
- 就是上面的直接打印对象
- printStackTrace
- JVM向控制台输出的信息就是调用这个方法,该方法打印的信息很全
finally
其实在try-catch后面还可以跟一个finally块,我们知道如果没有发生异常的话,catch里面的代码块是不会执行的,而finally里的代码块,无论是是否抛出异常,都一定会执行的,所以finally里面的代码多用来释放资源的。我们来看一个例子
try { |
这时是会抛出异常的,输出的结果为
可见finally里面的代码执行了,现在修改上面的代码为
try { |
这时是不会抛出异常的,所以catch代码块里面的程序不会被执行,但是finally里面的代码始终会被执行,执行的结果为
注意:
- 由于finally里面的代码一定是会执行的,所以要避免在finally里面写return语句,否则返回的一直是finally里面的结果。
多异常捕获
多次捕获,多次处理
即使用多个try-catch语句块
try { |
一次捕获,多次处理
即一个try匹配多个catch
try { |
如果在try中发生了异常,那么首先会判断这个异常能不能被异常1接收,如果能那么就在第一个catch中进行处理,如果不能,那么就判断能不能被异常2接收,以此类推。
这里需要注意的点是,父类异常必须子类异常后面。假设父类异常写在前面,如果发生了子类异常,根据多态,父类能够接收这个异常,所以写在后面的子类异常永远接受=收不到这个异常,就相当于是死代码了。
一次捕获,一次处理
就是在catch中使用Exception变量接收异常,因为所有的异常都是Exception的子类,所以可以接收所有的异常
try { |
子类注意事项
如果父类方法抛出多个异常,子类重写了该方法,那么子类方法有三种方案
- 抛出相同异常
- 抛出父类异常的子类
- 不抛出异常
如果父类的方法没有抛出异常,那么子类重写的方法也不能抛出异常。该子类产生的异常只能try-catch捕获处理,不能使用throws抛出。
自定义异常
自定义的异常必须继承一个异常类。如果继承的是Exception,那么该异常就是编译期异常,如果继承的是RuntimeException,那么就是运行时异常。下面简单的演示
public class MyException extends RuntimeException{ |
现在进行测试
public class TestMyException { |
结果为