Java:认识异常(超详解)

目录

📖一、异常的概念

📌1、算术异常

📌2、数组越界异常

​编辑

📌3、空指针异常

📖二、异常的结构和分类

📌1、异常的结构

📌2、异常的分类

🥏(1)编译时异常

🥏(2)运行时异常

📖三、异常的处理

📌1、防御式编程

🥏(1)事前防御型(LBYL)

🥏(2)事后认错型(EAFP)

📌2、异常的抛出

[📌 3、异常的捕获](#📌 3、异常的捕获)

🥏(1)异常声明throws

🥏(2)try-catch捕获并处理

🥏(3)finally

📌4、异常的处理流程

📖四、自定义异常类


📖一、异常的概念

在我们写代码的时候我们经常因为一些错误导致我们的代码不能够正常的执行,同时我们的编译器也会告诉我们出现了那些错误,比如我们写代码的时候经常遇到的几个错误:

📌1、算术异常

📌2、数组越界异常

📌3、空指针异常

这三种错误显然是 我们在写代码的时候经常犯的错误 ,而在Java中我们将这些错误,即在程序执行过程中发生的不正常行为称为异常。

显然我们能发现上述几种异常属于不同类型的异常,而在java中不同类型的异常,都有与其对应的类来进行描述,对于这些类就需要我们对异常的结构和分类进行简单的了解了 。

📖二、异常的结构和分类

📌1、异常的结构

从上图中可以看到:
Throwable:是异常体系的顶层类,其派生出两个重要的子类, Error 和 Exception
Error:指的是Java虚拟机无法解决的严重问题,比如:JVM的内部错误、资源耗尽等,典型代表:StackOverflowError和OutOfMemoryError,一旦发生回力乏术。
Exception:异常产生后程序员可以通过代码进行处理,使程序继续执行。

📌2、异常的分类

异常可能在编译时发生,也可能在程序运行时发生,因此我们根据发生的时机不同,可以将异常分为:编译时异常和运行时异常

🥏(1)编译时异常

在程序编译期间发生的异常,称为编译时异常,也称为受检查异常。

这就是所谓的编译时异常,当然既然有了异常那么我们就一定要来解决异常,对于他的解决方法让我先来了解完运行时异常之后再来关注。

🥏(2)运行时异常

在程序执行期间发生的异常,称为运行时异常,也称为非受检查异常。

RunTimeException以及其子类对应的异常,都称为运行时异常,对于他的子异常我们可以看上面的异常结构图片,当然对于其中的一些我们已经有所了解了,在上面我们所举的3个异常例子都是我们的运行时异常。

当然一些值得注意的是

编译时出现的语法性错误,不能称之为异常。例如System.out.println 拼写错了, 写成了system.out.println. 此时编译过程中就会出错, 这是 "编译期" 出错。而运行时指的是程序已经编译通过得到class 文件了, 再由 JVM 执行过程中出现的错误.

了解完异常的分类之后接下来就是我们要重点记住的异常的处理方式了 。

📖三、异常的处理

📌1、防御式编程

错误在代码中是客观存在的. 因此我们要让程序出现问题的时候及时了解到我们是哪里出现了错误,因此我们得到了两个得到错误位置的方式。

🥏(1)事前防御型(LBYL)

这种方式我们其实会发现一些缺陷,我们的正常流程和错误处理流程代码混在一起, 代码整体显的比较混乱。

🥏(2)事后认错型(EAFP)

在Java中事后认错型 是我们常用的异常处理方式,他的正常流程和错误流程是分离开的, 程序员更关注正常流程,代码更清晰,容易理解代码。

当然对于事后认错型我们发现我们的代码中使用了两个关键字:try和catch.

然而,对于事后认错型,我们有五个主要的关键字:throw、try、catch、final、throws。

那么对于这几个关键字让我们在下面进行深入的了解吧!

📌2、异常的抛出

在这之前我们已经成功的了解到了异常的位置,既然我们已经知道了有错误,接下来我们肯定需要将错误信息告知给调用者,这时就需要异常的抛出了。

语法

throw new XXXException("异常产生的原因");

注意

  • throw必须写在方法体内部
  • 抛出的对象必须是Exception 或者 Exception 的子类对象
  • 如果抛出的是 RunTimeException 或者 RunTimeException 的子类,则可以不用处理,直接交给JVM来处理
  • 如果抛出的是编译时异常,用户必须处理,否则无法通过编译
  • 异常一旦抛出,其后的代码就不会执行

📌 3、异常的捕获

既然我们已经得到了信息接下来就是要捕获并处理他,而异常的捕获,也就是异常的具体处理方式,主要有两种:异常声明throws 以及 try-catch捕获处理。

🥏(1)异常声明throws

处在方法声明时参数列表之后,当方法中抛出编译时异常,用户不想处理该异常,此时就可以借助throws将异常抛给方法的调用者来处理。即当前方法不处理异常,提醒方法的调用者处理异常。

语法:
修饰符 返回值类型 方法名(参数列表) throws 异常类型1,异常类型2...{
}

在上面我们曾遗留了一个问题如何对异常进行处理,而到这里,我们就可以成功的进行处理了

而我们知道了异常的声明方式,我们就可以成功的解决这个问题了

但这是我们就得到了几点注意事项

🐬1、throws必须跟在方法的参数列表之后

🐬 2、声明的异常必须是 Exception 或者 Exception 的子类

🐬3、调用声明抛出异常的方法时,调用者必须对该异常进行处理,或者继续使用throws抛出

🐬4、方法内部如果抛出了多个异常,throws之后必须跟多个异常类型,之间用逗号隔开,如果抛出多个异常类型 具有父子关系,直接声明父类即可。

多个异常类型

具有父子关系

我们要注意如果多种类型具有父子类关系,再次抛出只需要声明父类

🥏(2)try-catch捕获并处理

throws对异常并没有真正处理,而是将异常报告给抛出异常方法的调用者,由调用者处理。如果真正要对异常进行处理,就需要try-catch。

语法:

try{

// 将可能出现异常的代码放在这里

}catch(要捕获的异常类型 e){

// 如果try中的代码抛出异常了,此处catch捕获时异常类型与try中抛出的异常类型一致时, 或者是try中抛出异常的基类 时,就会被捕获到

// 对异常就可以正常处理,处理完成后,跳出try-catch结构,继续执行后序代码

}catch(要捕获的异常类型 e){

// 对异常进行处理

}finally{

// 此处代码一定会被执行到

}

// 后序代码

// 当异常被捕获到时,异常就被处理了,这里的后序代码一定会执行

// 如果捕获了,由于捕获时类型不对,那就没有捕获到,这里的代码就不会被执

我们来看一个简单的例子

这就是我们对异常的处理,但是我们发现原本编译器其实会告诉我们异常的信息,当现在我们并不知道异常是什么,那么我们该怎样得到异常信息呢?其实很简单只需要我们在catch处加上异常信息的打印即可。

当然由于异常的种类有很多, 因此我们要根据不同的业务场景来决定我们是否使用异常处理

  • 对于比较严重的问题(例如和算钱相关的场景), 应该让程序直接崩溃, 防止造成更严重的后果
  • 对于不太严重的问题(大多数场景), 可以记录错误日志, 并通过监控报警程序及时通知程序猿
  • 对于可能会恢复的问题(和网络相关的场景), 可以尝试进行重试
    注意 :

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

2. 如果抛出异常类型与catch时异常类型不匹配,即异常不会被成功捕获,也就不会被处理,继续往外抛,直到JVM收到后中断程序----异常是按照类型来捕获的

3. try中可能会抛出多个不同的异常对象,则必须用多个catch来捕获----即多种异常,多次捕获

但是注意我们是不能够捕获到多个异常的

如果多个异常的处理方式是完全相同, 对于这个代码我们是可以进行简化的:

但是我们并不是很建议这样来写,因为我们并不能明确的知道到底是什么导致的异常

如果异常之间具有父子关系,一定是子类异常在前catch,父类异常在后catch,否则语法错误,我们知道NullPointerException是Exception的子类所以当为空指针异常时我们可以写成这样:

但是如果我们想要这两异常都存在,我们一定要让子类异常在前catch,父类异常在后catch

否则的话

4. 可以通过一个catch捕获所有的异常,即多个异常,一次捕获(不推荐)

当然对于上述这种方式我们是不推荐的

🥏(3)finally

在写程序时,有些特定的代码,不论程序是否发生异常,都需要执行,比如程序中打开的资源:网络连接、数据库连接、IO流等,在程序正常或者异常退出时,必须要对资源进行回收。另外,因为异常会引发程序的跳转,可能导致有些语句执行不到,finally就是用来解决这个问题的。

语法:
try{
// 可能会发生异常的代码
}catch(异常类型 e){
// 对捕获到的异常进行处理
}finally{
// 此处的语句无论是否发生异常,都会被执行到
}
// 如果没有抛出异常,或者异常被捕获处理了,这里的代码也会执行

但是我们发现 finally 和 try-catch-finally 后的代码都会执行,那为什么还要有finally呢?

我们来看下面这段代码

上述程序,如果正常输入,成功接收输入后程序就返回了,try-catch-finally之后的代码根本就没有执行,即输入流 就没有被释放,造成资源泄漏。

然而finally中的代码一定会执行的,因此我们一般在finally中进行一些资源清理的扫尾工作。

当然在这里对于finally我们还有一个特殊的问题

这段代码的结果会是什么?我们可以先在文章的最下面来投个票

java 复制代码
public static void main(String[] args) {
        System.out.println(func());
    }
    public static int func() {
        try {
            return 10;
        } finally {
            return 20;
        }
    }

我们来揭晓答案了

这里其实会打印20,因为我们先返回了10,但是finally下面的语句一定会执行,因此会又有返回个20,这个20会把之前的结果覆盖掉,因此我们得到的结果将会是20

📌4、异常的处理流程

如果本方法中没有合适的处理异常的方式, 就会沿着调用栈向上传递,最后就会交给JVM,导致程序终止。

最后我们来对异常处理流程进行一个总结

  • 程序先执行 try 中的代码
  • 如果 try 中的代码出现异常, 就会结束 try 中的代码, 看和 catch 中的异常类型是否匹配.
  • 如果找到匹配的异常类型, 就会执行 catch 中的代码
  • 如果没有找到匹配的异常类型, 就会将异常向上传递到上层调用者.
  • 无论是否找到匹配的异常类型, finally 中的代码都会被执行到(在该方法结束之前执行).
  • 如果上层调用者也没有处理的了异常, 就继续向上传递.
  • 一直到 main 方法也没有合适的代码处理异常, 就会交给 JVM 来进行处理, 此时程序就会异常终止.

📖四、自定义异常类

Java 中虽然已经内置了丰富的异常类, 但是并不能完全表示实际开发中所遇到的一些异常,此时就需要维护符合我们实际情况的异常结构.

是否可以声明

当然,非受查异常也是可以声明的,所以我们一般在写代码的时候尽量写声明,这样能有效知道我们都有那些异常类型

注意

  • 自定义异常通常会继承自 Exception 或者 RuntimeException
  • 继承自 Exception 的异常默认是受查异常
  • 继承自 RuntimeException 的异常默认是非受查异常.

好了今天的内容就分享到这里了我们下一篇见! 记得点赞收藏加关注

相关推荐
tmacfrank1 分钟前
Coroutine 基础八 —— Flow 操作符(二)
android·开发语言·kotlin
苹果酱056710 分钟前
Golang的代码质量分析工具
java·vue.js·spring boot·mysql·课程设计
JoneMaster18 分钟前
[读书日志]从零开始学习Chisel 第三篇:Scala面向对象编程——类和对象(敏捷硬件开发语言Chisel与数字系统设计)
开发语言·嵌入式硬件·学习·硬件架构·scala
武子康18 分钟前
大数据-268 实时数仓 - ODS层 将 Kafka 中的维度表写入 DIM
java·大数据·数据库·数据仓库·分布式·mysql·kafka
silver988626 分钟前
reactor中的并发
java·reactor
java1234_小锋28 分钟前
Zookeeper是如何保证事务的顺序一致性的?
java·zookeeper·java-zookeeper
drebander2 小时前
Kotlin 协程与异步编程
开发语言·微信·kotlin
arong_xu2 小时前
C++23 格式化输出新特性详解: std::print 和 std::println
开发语言·c++·c++23
没明白白2 小时前
抽象类和接口的区别:你应该选择哪个?
java·开发语言
白宇横流学长2 小时前
基于Java的超级玛丽游戏的设计与实现【源码+文档+部署讲解】
java·开发语言·游戏