【Java】异常处理见解,了解,进阶到熟练掌握

各位看官早安午安晚安呀

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

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

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

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

1.1:throws的注意事项

1.2:throw的注意事项

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

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

[2.2. 如果抛出异常类型与catch时异常类型不匹配](#2.2. 如果抛出异常类型与catch时异常类型不匹配)

[2.3. try中可能会抛出多个不同的异常对象](#2.3. try中可能会抛出多个不同的异常对象)

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

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

关于 "调用栈"

4:finally

4:自定义异常类


Java 中,将程序执行过程中发生的不正常行为称为异常
要认识异常首先我们要认识一张图

从上图中可以看到:

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

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

java 复制代码
   System.out.println(10/0);


2. 数组越界异常

int [] arr = { 1 , 2 , 3 };
System . out . println ( arr [ 100 ]);

3:空指针异常

java 复制代码
        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 之后必须跟多个异常类型,之间用逗号隔开,如果抛出多个异常类型具有父子关系,直接声明父类即可。
java 复制代码
 
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块内抛出异常位置之后的代码将不会被执行

java 复制代码
 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收到后中断程序 ----异常是按照类型来捕获的

java 复制代码
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了

java 复制代码
  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(); 的方式查看出现异常代码的调用栈.

譬如上述代码:

java 复制代码
 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 就是用来解决这个问题的。

java 复制代码
 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 (被编译器当做一个警告).

java 复制代码
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:自定义异常类

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

java 复制代码
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输出太俗了,不能有效的提醒,这个时候我们就可以自定义一个异常类

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

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

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

java 复制代码
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异常处理了解,熟悉到进阶的全部内容了,能看到这里相信您一定对小编的文章有了一定的认可,异常的出现,让我们对于程序有了更多的认识~~~

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

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

相关推荐
古月居GYH7 分钟前
在C++上实现反射用法
java·开发语言·c++
儿时可乖了1 小时前
使用 Java 操作 SQLite 数据库
java·数据库·sqlite
ruleslol1 小时前
java基础概念37:正则表达式2-爬虫
java
xmh-sxh-13141 小时前
jdk各个版本介绍
java
天天扭码2 小时前
五天SpringCloud计划——DAY2之单体架构和微服务架构的选择和转换原则
java·spring cloud·微服务·架构
程序猿进阶2 小时前
堆外内存泄露排查经历
java·jvm·后端·面试·性能优化·oom·内存泄露
FIN技术铺2 小时前
Spring Boot框架Starter组件整理
java·spring boot·后端
小曲程序2 小时前
vue3 封装request请求
java·前端·typescript·vue