最近学习了java的异常,小结一下,有问题欢迎大佬指出。
基础概念
在说到异常之前,先说一下异常的关系网。
异常(Exception)和错误(Error)所属包分别是java.lang.Exception和java.lang.Error。这两个都是java.lang.Throwable这个类的子类。
异常:指java在运行过程,因为某些问题,程序突然停止并且抛出异常。
错误:指严重的问题带我们只能通过修改代码或者提高电脑性能去解决。比如我们创建一个数组,长度是int的最大值,理论上这是可以的,但我的电脑是16G内存,它无法在内存中创建这么长的数组,所以这种错误目前我们无法去改变,只能去尽力避免。
图1.Error的实例
对于异常,我们分两个大类:运行期异常和编译期异常。
运行期异常(RuntimeException):指所属RuntimeException类以及其子类的异常。我理解为,在编译期间编译器并没有给我报错,但是在运行程序时,程序却突然中断。比如数组的越界异常(ArrayIndexOutOfBoundsException)。假设还是一个数组arr,长度是5,但是我现在要输出arr[10]的数字,从语法角度看,没有问题,但是当程序运行时,JVM找不到这个位置所以才会报错。
图2. 运行期异常的实例
编译期异常(CheckedException):指非RuntimeException类以及其子类的异常。我理解为。在编译期间编译器就会给我们提示需要解决这个异常,我们必须解决或者抛出这个异常,程序才能执行。比如我们创建一个线程,当我们使用thread.sleep()方法时,我们需要用try catch去解决这个异常。
图3.编译期异常的实例
图4. 解决编译器异常后的正常输出
从图三图四,我们必须解决这个异常,那么为什么要解决呢?因为thread.sleep()这个方法的参数指的是毫秒,这个数据肯定是一个大于0的数,如果我们不小心输入了一个负数,一旦运行程序会直接崩溃并且报错,这时候如果有处理异常的操作,我们会知道这个异常是什么并且能找到它的位置快速解决这种问题。
图5.处理异常最大的好处
小结:异常不是说一定有错,而是某些代码我们或者程序认为可能会发生错误,所以我们需要处理它,如果发生异常,它抛出了异常,但是后面代码会继续执行。
如何处理异常
我们通过5个关键字去处理异常:try catch finally throw throws
try catch:这两个关键字一般一起使用,具体的语法格式是
try{ 可能发生错误的代码 }catch(什么类型的错误 ){ 如果出错,输出的代码 }
这里“可能发生错误的代码 ”可以是一行代码或者具有逻辑的几行代码。如果很多代码,没有逻辑关系,可以这样写
try{ 可能发生错误的代码 }catch(什么类型的错误 1 ){ 如果出错,输出的代码 1}catch(什么类型的错误 2){ 如果出错,输出的代码 2}catch(什么类型的错误 3){ 如果出错,输出的代码 4}
try catch finally:
try{ 可能发生错误的代码 }catch(什么类型的错误 ){ 如果出错,输出的代码 }finally{一定会输出的代码}
finally内的代码不管抛错还是不跑错,它都一定执行,可以理解为,抛错前我就执行了,只是代码位置在后面。
throw:
throw new 异常类型(参数);
这个参数可以是字符串去说明是什么异常,我们用throw是将错误抛给调用者,并不是说,这个异常被处理了,只是抛给了调用者,也就是调用的方法或者类,调用者还是要处理这个异常或者继续抛出调用者的调用者,总之异常一定要处理,抛出异常只是向调用者说明是什么异常。
throws:
语法例子:public void 方法名 (参数) throws 异常类型1, 异常类型2...{ }
与throw类似,只是throws只能写在方法的格式中,不能用在方法内,同时也是抛出异常不处理异常。
关于继承关系的异常
java的继承关系很重要的点,那么子类与父类的异常需要遵循一定的规则
a>如果父类没有声明异常,子类可以不声明异常或者声明运行期异常,但不能声明编译器异常。
b>如果父类声明运行期异常,子类可以不声明异常或者声明运行期异常,但不能声明编译器异常。
c>如果父类声明编译期异常,子类可以不声明异常或者声明运行期异常,如果什么编译期异常,只能声明与父类相同的编译期异常或者这个编译期异常的子类。
总结
异常是一个很重要的点,通过处理异常,我们能很快找到代码中出问题的地方以及具体的原因,对于规范代码以及维护软件十分重要。