异常时程序里面意外出现错误的情况,程序不是完美的 bug总会意外的出现,java坚持万物皆对象的原则,所以一个异常事件 java也会生成一个相应的对象类,我们把它们称为异常类
异常类的层次结构:
这张图头部其实没画完整,按上面说的一切皆对象 throwable 也继承自Object,所以类的一些基本方法 异常类都有。
throwable 被分成两大类:
Error
这一类 error 通俗意义上称之为 程序级别的错误,就是jvm整个都挂了,比如内存不足 内存溢出 ,这类错误直接可以让程序崩溃,根本不是代码层面能解决的了。 一旦遇到这种 程序层面基本束手待毙。
Exception
上一类 error 程序开发者无能为力,而这一类 异常 是程序开发者比较关注的一类,相对于error Exception是程序内相对可控的错误和以外,它是可以恢复的——比如常见的异常:参数错误(那你应该输入正确的参数可以修复它) 空指针对象 , 数组越界,class 找不到 等等等等。
所以举一个例子:
比如你手机里面的某个app打不开 或者账号登不上了 导致手机功能受阻,这个就属于Exception。 如果你手机被摔坏了 屏幕碎了 电池坏了 这种就是 error
对于Exception 有一个很重要的分类 就是 checked和unchecked异常。
就是程序编译的时候会强行帮你做检查,你必须把它显示的捕获或者抛出,不然编译就会报错,也就是说 你的程序连正常跑都跑不起来。
常见的checked异常 比如:如用户输入,数据库访问,网络异常,文件访问和丢失等。 这类异常属于程序无法控制的严重情况,如果你不显示的把它捕获或者抛出 置之不理的话,万一出现后果非常严重。 所以java的设计者就强行做了检测 强制你捕获和显示抛出这类异常。
最常见的例子 估计每一个java程序员都遇到过:就是比如我们写一个文件类
File file = new File();
你就会发现一写完 IDE就爆红了。
这时候 你需要显示的在方法中 throws 异常 或者捕获异常。
向上抛出异常, 捕获异常, 置之不理
要很好的理解这三种方式 首先要了解一下 java的
比如我们常见的一个场景 main方法—主方法 里面有一个方法A, 方法A里面调用了方法B 方法B里面调用了方法C:
C(方法内有步骤 1 2(出现异常) 3 )
|
B
|
A
|
Main
现在C里面的步骤2有异常出现了! 那么C方法会创建一个异常对象,把它交给运行时系统,这个对象里面包含错误的信息 错误的类型 还有错误发生时程序的状态。
这时候C里面可能自己就捕获了异常,用try catch。 如果没捕获,那么就会向上抛出这个异常,抛到B, 看B里面是否用try catch捕获了下层方法抛出的异常,如果捕获了就在B的catch里面处理,如果没捕获 就继续向上抛 到了A方法 这样层层递进。
如果系统搜索了这个调用堆栈上所有的方法 包括main方法,但是没有找到合适的异常处理程序,那么main方法就因此终止。
所以这就是异常处理的意义,有时候我们不希望 因为异常的出现 整个程序都挂掉。
比如上面的例子 C方法里面步骤 123. 2里面有异常出现 一直往上抛发现都没有处理这个异常,最后程序终止 那2后面的第3步肯定是没来及执行, 如果我们捕获了异常 ,那么虽然第二步 还是失败 但是和他没关系的第3步 可以执行成功 程序也可以继续往下运行。
需要注意的是,捕获异常并处理并不意味着异常不再存在或被修复,你不要指望catch帮你自动把这个异常修复掉,它只是提供了一种机制来控制异常的影响并继续执行后续的代码 说白了就是为了"顾全大局”。
所以当我们察觉到到某个地方可能出现异常的时候,这时候我们要结合实际场景 结合系统设计 思考一下,或者找你的技术leader商议一下:
这个异常到底是应该catch掉还是应该往上抛?
自定义异常其实很简单
/**
* 自定义异常的普遍标准就是 一般都是以Exception结尾,说明该类是一个异常类
* 一个无参构造和 和一个带异常信息的有参构造
*/
public class VcamlException extends RuntimeException {
public VcamlException(String message) {
super(message);
}
public VcamlException() {}
}
自定义异常一般用于 业务场景 ,有些场景 从代码逻辑上看不报错,但是业务上报错,这种情况你就可以手动 throw new VcamlException() 把这个异常往上抛
理解了上面的异常体系,下面就是具体代码中怎么 捕获异常和抛出异常, 写法很简单,但是新手可能没有把这个用法理解透,用的时候 会晕晕乎乎的。
对应上面的 异常处理体系 那一节的内容 下面实践体会一下
mian函数里面我们打印一头一尾, 中间调用底层的demo方法,如果demo中的异常被底层捕获了 那么main函数感知不到 就会接着执行
System.out.println("ending.....,程序没有因为异常而中断(底层方法捕获到了异常)");
如果异常抛出来了 到了main 我们用Exception 捕获 Exception 是所以异常的亲妈妈 可以捕获任何异常,捕获是为了证明这个异常被底层抛出来了,捕获到之后 我们不做处理 再往上抛 抛给jvm,程序就中断了 也就不会执行 后面的 打印ending 那一行
public static void main(String[] args) throws Exception {
System.out.println("starting.....");
try {
demo();
}catch(Exception e){
System.out.println("demo函数向上抛出了异常 (底层方法没有捕获到异常)");
// 模拟程序到此中断,下面的ending....不会打印
throw new Exception();
}
System.out.println("ending.....,程序没有因为异常而中断(底层方法捕获到了异常)");
}
首先最基本的检查异常 这类异常 是一定要显示的捕获或者声明的, 不然编译不通过。
那么这类异常 到底是捕获它还是声明往上抛 看具体情况和你自己的选择 ,这里面我们随便来个检查异常 比如找一个不存在的类:
//检查异常 必须强制抛出异常或者捕获
public static void demo1() throws ClassNotFoundException {
Class.forName("unknownClass");
}
这个一执行就会被main函数捕获到 程序就会终止
比如这个 这里数组越界了 那么它会抛出一个 数组越界异常 (RuntimeException 的子类),
所以这个catch 既可以用RuntimeException 也可以用 数组越界异常 两个都可以捕获。
public static void demo2(){
try {
int[] a = {1,2,3,4,5};
int num =a[7];
} catch (RuntimeException e) {
System.out.println("运行时异常了!");
e.printStackTrace();
}
}
结果就是:
程序没有中断 正常执行完了,异常打印出堆栈
starting.....
运行时异常了!
ending.....,程序没有因为异常而中断(底层方法捕获到了异常)
java.lang.ArrayIndexOutOfBoundsException: 7
at com.example.javacore.exception.Test.demo2(Test.java:12)
at com.example.javacore.exception.Test.main(Test.java:22)
Process finished with exit code 0
这里有一个重点就是:
catch()里面的异常只能捕获自身及子类 不能捕获父类!