时间:2022-04-16 10:00:44 | 栏目:JAVA代码 | 点击:次
本篇文章你会学习到什么是异常,异常的基本语法使用,和自定义异常,干货多多!!!

我们曾经的代码中已经接触了一些 “异常” 了. 例如
除以 0
public static void main(String[] args) {
System.out.println(10 / 0);
}
算术异常:

数组越界
int[] arr = {1, 2, 3};
System.out.println(arr[100]);
// 执行结果
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 100
空指针异常
public class Test {
public int num = 10;
public static void main(String[] args) {
Test t = null;
System.out.println(t.num);
}
}
// 执行结果
Exception in thread "main" java.lang.NullPointerException
运行时异常(非受查异常)
编译时异常(受查异常)


我们来看一下空指针异常(其实是一个类继承了运行时异常)

对应上面的图,
在看一下运行时异常:继承了Exception(也可以看上面的图)

看一下Exception 继承了Throwable:

通过这个图我们得到一个结论:每一个异常都是一个类,异常之间的关系 参考图上继承
我们看看Error

这个就叫做错误

异常和错误的区别:
错误:必须由程序员处理逻辑错误
异常:处理异常就OK了接下来继续看
错误在代码中是客观存在的. 因此我们要让程序出现问题的时候及时通知程序猿. 我们有两种主要的方式
LBYL: Look Before You Leap. 在操作之前就做充分的检查.
EAFP: It's Easier to Ask Forgiveness than Permission. “事后获取原谅比事前获取许可更容易”. 也就是先操作, 遇到问题再处理.
注意!!! 上面这两个概念千万不要背!
其实很好理解, 举个栗子~~
比如老湿年轻的时候, 和你们师娘刚开始谈对象. 我们都知道, 谈对象需要有一些亲密的动作, 比如 “拉小手” 这 种. emmmmm 问题来了, 老湿去拉师娘的小手有两种方式:
a) 老湿说, 妹子, 我拉你小手可以嘛? 获取妹子的同意后, 再拉手(这就是 LBYL).
b) 老湿趁妹子不备, 直接拉住. 大不了妹子生气了给老湿一巴掌, 老湿再道歉就是(这就是 EAFP).
异常的核心思想就是 EAFP.
例如, 我们用伪代码演示一下开始一局王者荣耀的过程.
boolean ret = false;
ret = 登陆游戏();
if (!ret) {
处理登陆游戏错误;
return; }
ret = 开始匹配();
if (!ret) {
处理匹配错误;
return; }
ret = 游戏确认();
if (!ret) {
处理游戏确认错误;
return; }
ret = 选择英雄();
if (!ret) {
处理选择英雄错误;
return; }
ret = 载入游戏画面();
if (!ret) {
处理载入游戏错误;
return; }
try {
登陆游戏();
开始匹配();
游戏确认();
选择英雄();
载入游戏画面();
...
} catch (登陆游戏异常) {
处理登陆游戏异常;
} catch (开始匹配异常) {
处理开始匹配异常;
} catch (游戏确认异常) {
处理游戏确认异常;
} catch (选择英雄异常) {
处理选择英雄异常;
} catch (载入游戏画面异常) {
处理载入游戏画面异常; }
对比两种不同风格的代码, 我们可以发现, 使用第一种方式, 正常流程和错误处理流程代码混在一起, 代码整体显的比较混乱. 而第二种方式正常流程和错误流程是分离开的, 更容易理解代码

代码:
public static void main(String[] args) {
int[] arr = {1, 2, 3};
System.out.println("before");
System.out.println(arr[100]);
System.out.println("after");
}

解释:

我们发现一旦出现异常, 程序就终止了. after 没有正确输出
为什么?
当没有处理异常的时候一旦程序发生了异常之后,这个异常会交给jvm,如果给jvm处理异常,那么程序一定会终止。
我们来自己处理异常:catch一定要捕获相应的异常(没有捕获到就交给了JVM了)

结果:

但是下面的也不会执行了

相比上面的我们处理了异常,让程序可以继续运行下去了
那上面是没有异常消息的提示了,我们还想要些提示怎么搞呢??
使用:e.printStackTrace(); after还是正常出来

这个红字可以进行参考。
代码示例 catch 可以有多个:

一段代码可能会抛出多种不同的异常, 不同的异常有不同的处理方式. 因此可以搭配多个 catch 代码块.
如果多个异常的处理方式是完全相同, 也可以写成这样

代码示例 也可以用一个 catch 捕获所有异常(不推荐)
int[] arr = {1, 2, 3};
try {
System.out.println("before");
arr = null;
System.out.println(arr[100]);
System.out.println("after");
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("after try catch");
为什么不推荐了,因为exception 范围太大了,不好排查
代码示例 finally 的执行不需要条件
int[] arr = {1, 2, 3};
try {
System.out.println("before");
arr = null;
System.out.println(arr[100]);
System.out.println("after");
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("finally code");
}

finally 无论有没有异常都会最后执行
代码示例 使用 try 负责回收资源
Scanner.close()可以释放资源可以写到finally里面,也可以直接写到try里面
try (Scanner sc = new Scanner(System.in)) {
int num = sc.nextInt();
System.out.println("num = " + num);
} catch (Exception e) {
e.printStackTrace();
}
代码示例 如果向上一直传递都没有合适的方法处理异常, 最终就会交给 JVM 处理, 程序就会异常终止(和我们最开始未使用 try catch 时是一样的).
public static void main(String[] args) {
try {
func();
} catch (ArrayIndexOutOfBoundsException e) {
e.printStackTrace();
}
System.out.println("after try catch");
}
public static void func() {
int[] arr = {1, 2, 3};
System.out.println(arr[100]);
}
// 直接结果
java.lang.ArrayIndexOutOfBoundsException: 100
at demo02.Test.func(Test.java:18)
at demo02.Test.main(Test.java:9)
after try catch
可以看见上面代码是func可以有异常,但是在main方法里面处理了的
异常的种类有很多, 我们要根据不同的业务场景来决定.
对于比较严重的问题(例如和算钱相关的场景), 应该让程序直接崩溃, 防止造成更严重的后果
对于不太严重的问题(大多数场景), 可以记录错误日志, 并通过监控报警程序及时通知程序猿
对于可能会恢复的问题(和网络相关的场景), 可以尝试进行重试.
在我们当前的代码中采取的是经过简化的第二种方式. 我们记录的错误日志是出现异常的方法调用信息, 能很快
速的让我们找到出现异常的位置. 以后在实际工作中我们会采取更完备的方式来记录异常信息
除了 Java 内置的类会抛出一些异常之外, 程序猿也可以手动抛出某个异常. 使用 throw 关键字完成这个操作
public static void main(String[] args) {
System.out.println(divide(10, 0));
}
public static int divide(int x, int y) {
if (y == 0) {
throw new ArithmeticException("抛出除 0 异常");
}
return x / y;
}
// 执行结果
Exception in thread "main" java.lang.ArithmeticException: 抛出除 0 异常
at demo02.Test.divide(Test.java:14)
at demo02.Test.main(Test.java:9)
在这个代码中, 我们可以根据实际情况来抛出需要的异常. 在构造异常对象同时可以指定一些描述性信息.
异常说明
我们在处理异常的时候, 通常希望知道这段代码中究竟会出现哪些可能的异常.
我们可以使用 throws 关键字, 把可能抛出的异常显式的标注在方法定义的位置. 从而提醒调用者要注意捕获这些异常.
public static int divide(int x, int y) throws ArithmeticException {
if (y == 0) {
throw new ArithmeticException("抛出除 0 异常");
}
return x / y;
}
Java 中虽然已经内置了丰富的异常类, 但是我们实际场景中可能还有一些情况需要我们对异常类进行扩展, 创建符合我们实际情况的异常.
我们先看一下其他的异常大概是个怎么样的一个体系:

空指针异常是继承了个运行时异常,不过他里面的方法写的不是很多,也就是两个帮父类的构造方法,所以按照它这样的我们也可以写一个自己的异常。
创建一个异常类:

使用:

结果:

以上就是我们的一个自定义异常
那么我们可不可以继承Exception呢?

这里发现报错了,为什么?我们在来看一下异常体系结构

这个时候编译器不知道是编译时异常还是运行时异常,所以默认选择权限小的编译时异常,这个时候我们要抛出异常

上面的没有报错了下面的开始了?为什么?因为我们抛出编译时异常,我们要try catch一下:

所以这个就是一个自定义异常。
在举一个例子:
例如, 我们实现一个用户登陆功能:(如果用户名错误处理用户名的错误,密码错误处理密码错误)

此时我们在处理用户名密码错误的时候可能就需要抛出两种异常. 我们可以基于已有的异常类进行扩展(继承), 创建和我们业务相关的异常类

自己写的类
我们可以在login里面这样写:

主方法:

这样就是使用我们自己的异常。
注意事项:
以上就是异常的全部内容了,如果有什么不对的地方欢迎评论指正谢谢!!