【Java】异常处理见解,了解,进阶到熟练掌握 【Java】异常处理见解,了解,进阶到熟练掌握,让我们一起来探索异常的美妙吧~~~

各位看官早安午安晚安呀

如果您觉得这篇文章对您有帮助的话

欢迎您一键三连,小编尽全力做到更好
欢迎您分享给更多人哦

大家好我们今天来学习Java面向对象的的抽象类和接口,我们大家庭已经来啦~

Java中,将程序执行过程中发生的不正常行为称为异常
要认识异常首先我们要认识一张图
从上图中可以看到:
1. Throwable是异常体系的顶层类,其派生出两个重要的子类, Error Exception
2. Error指的是Java虚拟机无法解决的严重问题,比如:JVM的内部错误、资源耗尽等,典型代表:
StackOverflowErrorOutOfMemoryError,一旦发生回力乏术。
3. Exception异常产生后程序员可以通过代码进行处理,使程序继续执行。比如:感冒、发烧。我们平时所说的异常就是Exception
异常大概处理方案:

关于异常的一般处理方式:

异常的种类有很多, 我们要根据不同的业务场景来决定.
对于比较严重的问题(例如和算钱相关的场景), 应该让程序直接崩溃, 防止造成更严重的后果
对于不太严重的问题(大多数场景), 可以记录错误日志, 并通过监控报警程序及时通知程序猿
对于可能会恢复的问题(和网络相关的场景), 可以尝试进行重试
在我们当前的代码中采取的是经过简化的第二种方式. 我们记录的错误日志是出现异常的方法调用信息, 能很快速的让我们找到出现异常的位置. 以后在实际工作中我们会采取更完备的方式来记录异常信息
比如我们之前写代码时经常遇到的:
1: 算数异常
System.out.println(10/0);
2.  数组越界异常
int[] arr = {1, 2, 3};
System.out.println(arr[100]);
3:空指针异常
int [] array = null;
 System.out.println(array.length);

这些异常,我们写代码的时候不会显示异常,但是程序运行时就会报错;

(这种异常我们称之为运行时异常也叫非受查异常

既然有非受查异常,就自然会有受查异常

譬如:我们之前一直没明白的克隆方法,我们子类要用这个克隆方法,克隆之后肯定要用一个新的对象来接收,那就是Person person2 = person1.clone();但是clone()是Object类的方法,我们只能在类里通过super调用,所以子类要用就必须要重写这个方法。

但是重写之后还是会报异常CloneNotSupportedException

我们只要调用这个方法就会报异常,但是我们还是要用到这个方法

所以说现在摆在我们面前的有两条路:

1.我们不管这个异常,把这个异常抛出去

2:我们主动地去捕捉这个异常

1.(throws和throw)我们不管这个异常,把这个异常抛出去

抛出去这个异常就要引出一个关键字throws了

然后还有一个关键字throw和它很像(这个关键字也是抛出一个异常)

他俩的区别在于:

throws是抛出已经出现的异常(一般放在方法参数列表之后)(也算是处理了,但不算是真正的处理,只是异常报告给抛出异常方法的调用者,由调用者处理

而throw是我们主动去抛出一个异常,一般是程序没出现我们为了提醒别人而抛出的异常

但是这个异常迟早是要被捕捉的,不然异常一直存在,最后大家都处理,只能交给JVM去处理,最后程序就只能报出来这个异常

我们throws出去这个异常

大家可以看到这个时候就不报异常了,但是我们现在调用它试试呢?

现在它又报错了,为啥呢?就像我上面所说的你只是抛出了这个异常(暂时解决了这个问题)实际上是把问题抛给了这个方法的调用者

然后我们还不想解决,那就,让main函数也把这个责任甩出去

按理说程序运行时,JVM会处理这个异常

但是我们实现了一个克隆接口(允许)该类的对象被拷贝

Cloneable接口是一个标记接口(即它没有任何方法定义)。当一个类实现了Cloneable接口,这表示这个类的对象支持克隆操作,而且Person类实现了Cloneable接口并且正确地重写了clone()方法,所以说最终没有抛出这个异常

1.1:throws的注意事项

处在方法声明时参数列表之后,当方法中抛出编译时异常,用户不想处理该异常,此时就可以借助throws将异常抛给方法的调用者来处理。即当前方法不处理异常,提醒方法的调用者处理异常
注意:
1. throws必须跟在方法的参数列表之后
2. 声明的异常必须是 Exception 或者 Exception 的子类
3. 方法内部如果抛出了多个异常,throws之后必须跟多个异常类型,之间用逗号隔开,如果抛出多个异常类型具有父子关系,直接声明父类即可。
publicstaticvoidmain(String[]args)throwsArrayIndexOutOfBoundsException,ArithmeticException{}
可以替换成
public static void main(String[] args) throws Exception{
}

4:调用声明抛出异常的方法时,调用者必须对该异常进行处理,或者继续使用throws抛出
(我们讲述的clone方法和main方法)
5: 将光标放在抛出异常方法上,ALT点击+enter快速处理

1.2:throw的注意事项

1.throw必须写在方法体内部
2. 抛出的对象必须是Exception 或者 Exception 的子类对象
3. 如果抛出的是 RunTimeException 或者 RunTimeException 的子类,则可以不用处理,直接交给JVM来处理
4. 如果抛出的是编译时异常,用户必须处理,否则无法通过编译
throws继续抛出或者try-catch处理,最后try-catch不处理的话JVM处理,程序异常终止
5. 异常一旦抛出,其后的代码就不会执行

2:(try-catch)我们主动地去捕捉这个异常

就像如果我们捕捉了上面的克隆异常(但是他是一个异常,还是不会被执行,所以我们最好还是实现一个克隆接口,最终别抛出异常)

如果我们要捕捉异常

这时候就要引出一个组合了 try-catch捕获并处理(细节我们一会会具体将到)

try{
// 将可能出现异常的代码放在这里
}catch(要捕获的异常类型 e){
// 如果try中的代码抛出异常了,此处catch捕获时异常类型与try中抛出的异常类型一致时,或者是try中抛出异常的基类时,就会被捕获到
// 对异常就可以正常处理,处理完成后,跳出try-catch结构,继续执行后序代码
}finally{
// 此处的语句无论是否发生异常,都会被执行到
}
// 如果没有抛出异常,或者异常被捕获处理了,这里的代码也会执行

示例:

2.1. try块内抛出异常位置之后的代码将不会被执行

public static void main(String[] args){
 try{
 System.out.println(10/0);
 System.out.println("想你");
 }catch(ArithmeticException e){
 System.out.println("捕捉到了算数异常");
 }
 }

这里我们捕捉到了算数异常,但是“想你”并没有被打印这是因为这里程序运行到这里已经发现了异常,就会去捕捉(因为我们用try-catch组合了)所以后面的代码就不会被执行了

就像程序这里(哈哈打印了,想你没有打印)

2.2. 如果抛出异常类型与catch时异常类型不匹配

即异常不会被成功捕获,也就不会被处理,继续往外抛,直到 JVM收到后中断程序----异常是按照类型来捕获的

public static void main(String[] args){
 try{
 System.out.println(10/0);
 System.out.println("想你");
 }catch(NullPointerException e){
 System.out.println("捕捉到了算数异常");
 }
 System.out.println("想你");
 }

一个想你都没有打印

2.3. try中可能会抛出多个不同的异常对象

则必须用多个catch来捕获----即多种异常,多次捕获 ,并且捕获到了一个异常,就不会进入另一个catch了

public static void main(String[] args){
 try{
 System.out.println(10/0);
 int[] arr = {1, 2, 3};
 System.out.println(arr[100]);
 }catch(NullPointerException e){
 System.out.println("捕获到了空指针异常");
 }
 catch(ArithmeticException e){
 System.out.println("捕捉到了算数异常");
 }
 System.out.println("想你");
 }

这时候就有小伙伴问了,那另一个异常去哪了?

其实我们上面就已经讲述过了,一个异常出现,我们用try-catch捕获处理(这个异常下面的代码就不会执行了,直接执行捕获后的catch里面的代码,以及处理完程序后面的代码)

2.4:我们也可以有以下两种不推荐的捕获方式

1.

catch (ArrayIndexOutOfBoundsException | NullPointerException e) {
...
}
不过我们不推荐这种方式:太模糊了,到底是哪种异常
2.一次性捕获所有异常:
catch( Exception e){    这是异常的父类
}
注意: 如果异常之间具有父子关系,一定是子类异常在前catch,父类异常在后catch,否则语法错误:(否则,相当于,你在前面就把水全部堵住了,异常根本流过来)
子类异常在前catch,父类异常在后catch(还是有点作用的,防止程序异常终止嘛)

3:关于 "调用栈"(e.printStackTrace()

关于 "调用栈"

方法之间是存在相互调用关系的, 这种调用关系我们可以用 "调用栈" 来描述. 在 JVM 中有一块内存空间称为 "虚拟机栈" 专门存储方法之间的调用关系. 当代码中出现异常的时候, 我们就可以使用 e.printStackTrace(); 的方式查看出现异常代码的调用栈.

譬如上述代码:

public static void main(String[] args){
 try{
 System.out.println(10/0);
 int[] arr = {1, 2, 3};
 System.out.println(arr[100]);
 }catch(NullPointerException e){
 System.out.println("捕获到了空指针异常");
 }
 catch(ArithmeticException e){
 System.out.println("捕捉到了算数异常");
 e.printStackTrace();
 }
 System.out.println("想你");
 }

4:finally

在写程序时,有些特定的代码,不论程序是否发生异常,都需要执行,比如程序中打开的资源:网络连接、数据库 连接,IO流等,在程序正常或者异常退出时,必须要对资源进进行回收
另外,因为异常会引发程序的跳转,可能导致有些语句执行不到finally就是用来解决这个问题的。
public static void main(String[] args){
 try {
 System.out.println(10 / 0);
 int[] arr = {1, 2, 3};
 System.out.println(arr[100]);
 }
 catch (ArrayIndexOutOfBoundsException e){
 System.out.println("捕获到了数组越界异常");
 }finally {
 System.out.println("不管是否捕获到了异常,finally后面的都会被执行");
 }
 System.out.println("想你");
 }

就是想你没有被执行(finally就是用来处理善后工作的)

譬如关闭输入流

大家可以想想如果没有finally,这个程序刚才没有捕捉到异常,就异常终止了,但是输入流没有没关闭(这时候就造成了资源的泄露)

注意:一般我们不建议在 finally 中写 return (被编译器当做一个警告).

public static void main(String[] args) {
System.out.println(func());
}
public static int func() {
try {
return 10;
} finally {
return 20;
}
}
A: 10 B: 20 C: 30 D: 编译失败

选择B

finally 执行的时机是在方法返回之前(try 或者 catch 中如果有 return 会在这个 return 之前执行 finally).
但是如果
finally 中也存在 return 语句, 那么就会执行 finally 中的 return, 从而不会执行到 try 中原有的 return.
一般我们不建议在 finally 中写 return (被编译器当做一个警告)
所以说我们这两个问题大家清楚了吗?
1. throw throws 的区别?
2. finally中的语句一定会执行吗?
异常处理流程总结
程序先执行 try 中的代码
如果 try 中的代码出现异常, 就会结束 try 中的代码, 看和 catch 中的异常类型是否匹配.
如果找到匹配的异常类型, 就会执行 catch 中的代码
如果没有找到匹配的异常类型, 就会将异常向上传递到上层调用者
.
无论是否找到匹配的异常类型, finally 中的代码都会被执行到(在该方法结束之前执行).
如果上层调用者也没有处理的了异常, 就继续向上传递.
一直到 main 方法也没有合适的代码处理异常, 就会交给 JVM 来进行处理, 此时程序就会异常终止.

4:自定义异常类

接下来我们实现一个登录系统

public class LogIn {
 private String userName = "admin";
 private String password = "123456";
 public void loginInfo(String userName, String password) {
 if (!this.userName.equals(userName)) {
 System.out.println("名字错误");
 return;
 }
 if (!this.password.equals(password)) {
 System.out.println("密码错误");
 return;
 }
 System.out.println("登陆成功");
 }
 public static void main(String[] args) {
 LogIn logIn = new LogIn();
 logIn.loginInfo("admin", "1234561");
 }
}

但是总是sout输出太俗了,不能有效的提醒,这个时候我们就可以自定义一个异常类

但是我们应该如何定义一个异常类呢?(并且运行时能给我们检测出来)我们不妨看看其他异常类是怎么定义的?譬如:算数异常

那么我们也这样写两个异常

然后我们的代码就可以改成这个样子

public class LogIn {
 private String userName = "admin";
 private String password = "123456";
 public void loginInfo(String userName, String password) throws UserNameException,UserPasswordException {
 if (!this.userName.equals(userName)) {
 throw new UserNameException("用户名错误");
 }
 if (!this.password.equals(password)) {
 throw new UserPasswordException("登录密码错误");
 }
 System.out.println("登陆成功");
 }
 public static void main(String[] args) {
 LogIn logIn = new LogIn();
 try{
 logIn.loginInfo("admin", "1234561");
 }catch(UserNameException e){
 System.out.println("");
 }
 }
}

登录密码错误

其实定义方式就是这个

1. 自定义异常类,然后继承自Exception 或者 RunTimeException
2. 实现一个带有String类型参数的构造方法,参数含义:出现异常的原因

注意继承Exception时是编译异常 ,继承RunTimeException是受查时异常

所以说,继承RunTimeException时就不需要抛出异常了(写代码时也不会报异常),如果我们不处理,运行时JVM会帮我们处理(最后返回发生异常的位置)

大家可以看一下如果我继承Exception时不抛出异常,这个时候就会报错,因为继承Exception是编译时异常(受查异常)

总之,大家根据自己的需求去选择是用编译时异常还是运行时异常就好了

上述就是 Java异常处理了解,熟悉到进阶的全部内容了,能看到这里相信您一定对小编的文章有了一定的认可,异常的出现,让我们对于程序有了更多的认识~~~

有什么问题欢迎各位大佬指出
欢迎各位大佬评论区留言修正

您的支持就是我最大的动力​​​!!!!

作者:郑州吴彦祖772原文地址:https://blog.csdn.net/2302_80639556/article/details/142962576

%s 个评论

要回复文章请先登录注册