Java异常机制:从混乱到控制的错误管理艺术

在这里插入图片描述

  • 👑专栏内容:Java
  • ⛪个人主页:子夜的星的主页
  • 💕座右铭:前路未远,步履不停

目录

  • 一、异常的体系结构
    • 1、异常的体系结构
    • 2、异常的分类
  • 二、异常的处理
    • 1、异常的抛出
    • 2、异常的捕获
      • 2.1、异常声明`throws`
      • 2.2、`try-catch`捕获并处理
      • 2.3、`finally`
    • 3、异常的处理流程
      • 3.1、什么是 "调用栈"
      • 3.2、异常处理流程总结
  • 三、自定义异常类


在程序运行过程中,会遇见一些奇奇怪怪的问题,有时候通过代码是很难控制的。在 Java 中,将程序执行过程中发生的不正常行为称之为异常。

一、异常的体系结构

异常种类有很多,为了对不同异常或者错误进行很好的分类管理。

Java内部维护了一个异常的体系结构:
在这里插入图片描述

1、异常的体系结构

Throwable 是 Java异常体系的顶层类,是Java语言中所有错误和异常的超类。它派生出两个重要的子类:ErrorException

Error 指的是Java虚拟机(JVM)无法解决的严重问题,通常不应由合理的应用程序尝试捕获。大多数此类错误是JVM遇到的异常情况。此类异常一旦发生,一般没有办法通过修改代码解决。

Exception指的是程序员可以通过代码进行处理,使程序继续执行的异常。我们平时所说的“异常”通常是指Exception

2、异常的分类

异常可能在编译时发生,也可能在程序运行时发生,根据发生的时机不同,可以将异常分为:运行时异常和编译时异常(受检查异常)。

  • 编译时异常

在程序编译期间发生的异常,称为编译时异常,也称为受检查异常(Checked Exception)。这些异常在编译期间会被检查,也就是说,如果一个方法可能抛出某个受检查异常,那么在调用这个方法的地方必须对这个异常进行处理。

这种处理可以是捕获异常并对其进行处理,或者是将异常声明在方法的throws子句中,告诉方法的调用者需要处理这个异常。

  • 运行时异常

运行时异常,也称为非受检查异常(Unchecked Exceptions),与编译时异常不同,运行时异常在编译期间不需要显式处理。这意味着如果你的代码抛出了一个运行时异常,你不需要用try-catch块捕获它,也不需要在方法声明中用throws子句声明它。

二、异常的处理

异常处理主要的5个关键字:throwtrycatchfinalthrows

1、异常的抛出

throw关键字是用来显式地抛出一个异常的。使用throw可以抛出一个现有的异常实例或创建一个新的异常实例并抛出。具体的语法如下:

throw new XXXException("需要显示的错误信息");

我们一般通过throw抛出一些自己自定义的异常。

public class ArrayElementRetriever {// getElement方法尝试从一个整数数组中获取指定索引处的元素public static int getElement(int[] array, int index) {// 检查传入的数组是否为null。如果是null,抛出NullPointerException。if (null == array) {throw new NullPointerException("传递的数组为null");}// 检查索引是否越界。如果索引小于0或大于等于数组长度,抛出ArrayIndexOutOfBoundsException。if (index < 0 || index >= array.length) {throw new ArrayIndexOutOfBoundsException("传递的数组下标越界");}// 如果没有异常发生,则返回数组中指定索引处的元素。return array[index];}public static void main(String[] args) {int[] array = {1, 2, 3};// 尝试获取数组中的元素。这里故意传递一个越界的索引。getElement(array, 3);}
}

【注意事项】

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

2、异常的捕获

异常的捕获,也就是异常的具体处理方式。异常的捕获主要有两种方式:异常声明throws 以及 try-catch捕获处理。

2.1、异常声明throws

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

修饰符 返回值类型 方法名(参数列表) throws 异常类型1,异常类型2...{
}
public class ExceptionThrower {// 定义一个方法,声明它可能抛出一个自定义的编译时异常public void doSomethingRisky() throws CustomException {// 假设这里有一些逻辑,最后确定需要抛出异常throw new CustomException("描述错误情况");}public static void main(String[] args) {ExceptionThrower thrower = new ExceptionThrower();try {// 尝试调用可能抛出异常的方法thrower.doSomethingRisky();} catch (CustomException e) {// 处理异常System.out.println("捕获到异常: " + e.getMessage());}}
}// 自定义编译时异常
class CustomException extends Exception {public CustomException(String message) {super(message);}
}

【注意事项】

  1. throws关键字用于方法声明中,紧跟在方法的括号之后。
  2. throws后面可以跟一个或多个异常类型,使用逗号分隔。
  3. 如果一个方法声明抛出一个异常,那么调用这个方法的代码必须处理这个异常,要么是通过try-catch捕获它,要么是通过在其自身声明中使用throws继续向上抛出。
  4. 使用throws声明的异常类型应该是具体的异常类,而不是抽象类或接口。
  5. 对于运行时异常(RuntimeException及其子类),通常不在方法声明中使用throws进行声明,因为它们是非受检查的异常,但你也可以这样做以提供更好的程序文档。

2.2、try-catch捕获并处理

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

try{// 将可能出现异常的代码放在这里
}catch(要捕获的异常类型 e){// 如果try中的代码抛出异常了,此处catch捕获时异常类型与try中抛出的异常类型一致时,或者是try中抛出异常的基类时,就会被捕获到// 对异常就可以正常处理,处理完成后,跳出try-catch结构,继续执行后序代码
}[catch(异常类型 e){// 对异常进行处理
}finally{// 此处代码一定会被执行到
}]// 后序代码// 当异常被捕获到时,异常就被处理了,这里的后序代码一定会执行// 如果捕获了,由于捕获时类型不对,那就没有捕获到,这里的代码就不会被执行

注意:

  1. []中表示可选项,可以添加,也可以不用添加
  2. try中的代码可能会抛出异常,也可能不会
public class demo{public static void main(String[] args) {System.out.println("before");try{System.out.println(10/0);}catch (ArithmeticException e){System.out.println("捕获到了 ArithmeticException 这个异常");}System.out.println("affer");}
}

在这里插入图片描述 try块内抛出异常位置之后的代码将不会被执行,如果抛出异常类型与catch时异常类型不匹配,即异常不会被成功捕获,也就不会被处理,继续往外抛,直到JVM收到后中断程序。

public class demo1 {public static void main(String[] args) {System.out.println("before");try{System.out.println(10/0);}catch (NullPointerException e){System.out.println("捕获到了 ArithmeticException 这个异常");}System.out.println("affer");}
}

在这里插入图片描述try中可能会抛出多个不同的异常对象,则必须用多个catch来捕获,即多种异常,多次捕获

public class demo1 {public static void main(String[] args) {int[] arr = {1, 2, 3};try {System.out.println("before");// arr = null;System.out.println(arr[100]);System.out.println("after");} catch (ArrayIndexOutOfBoundsException e) {System.out.println("这是个数组下标越界异常");e.printStackTrace();} catch (NullPointerException e) {System.out.println("这是个空指针异常");e.printStackTrace();}System.out.println("after try catch");}
}

在这里插入图片描述如果多个异常的处理方式是完全相同, 也可用写成下面这个样子:

catch (ArrayIndexOutOfBoundsException | NullPointerException e) {}

虽然可用像上面这样写,但是不建议。因为没办法知道到底是因为那个异常导致的。衡量一个代码的好与坏,除了时间复杂度和空间复杂度之外,真正要看的是代码的可读性。

如果异常之间具有父子关系,一定是子类异常在前catch,父类异常在后catch,否则语法错误:

public class demo1 {public static void main(String[] args) {int[] arr = {1, 2, 3};try {System.out.println("before");arr = null;System.out.println(arr[100]);System.out.println("after");} catch (Exception e) { // Exception可以捕获到所有异常e.printStackTrace();}catch (NullPointerException e){ // 永远都捕获执行到e.printStackTrace();}System.out.println("after try catch");}
}

在这里插入图片描述 可以通过一个catch捕获所有的异常,即多个异常,一次捕获(不推荐)

public class demo1 {public static void main(String[] args) {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 类是所有异常类的父类,因此可以用这个类型表示捕捉所有异常。

catch 进行类型匹配的时候,不光会匹配相同类型的异常对象, 也会捕捉目标异常类型的子类对象。如刚才的代码,NullPointerExceptionArrayIndexOutOfBoundsException 都是 Exception 的子类,因此都能被捕获到。

2.3、finally

在写程序时,有些特定的代码,不论程序是否发生异常,都需要执行,比如程序中打开的资源:网络连接、数据库连接、IO流等,在程序正常或者异常退出时,必须要对资源进进行回收。

另外,因为异常会引发程序的跳转,可能导致有些语句执行不到,finally就是用来解决这个问题的。finally的语法格式如下:

try{// 可能会发生异常的代码
}catch(异常类型 e){// 对捕获到的异常进行处理
}finally{// 此处的语句无论是否发生异常,都会被执行到
}// 如果没有抛出异常,或者异常被捕获处理了,这里的代码也会执行
public class demo1 {public static void main(String[] args) {try{int[] arr = {1,2,3};arr[100] = 10;arr[0] = 10;}catch (ArrayIndexOutOfBoundsException e){System.out.println(e);}finally {System.out.println("finally中的代码一定会执行");}System.out.println("如果没有抛出异常,或者异常被处理了,try-catch后的代码也会执行");}
}

在这里插入图片描述
【问题】 既然 finallytry-catch-finally 后的代码都会执行,那为什么还要有finally呢?

虽然finally 块和try-catch 结构后的代码在很多情况下都会执行,finally 仍然非常重要,原因主要包括:

  1. 确保资源释放: 最常见的finally用途是进行资源清理,比如关闭文件流或数据库连接。这些操作即使在发生异常时也必须执行,以防止资源泄漏。如果只将清理代码放在try-catch之后,那么在异常未被当前的catch捕获时,这些清理代码将不会执行。

  2. 处理未捕获的异常: 如果try块中抛出了一个未被任何catch捕获的异常,try-catch之后的代码不会执行,但finally块仍然会执行。这为处理所有情况提供了一个统一的地方,确保即使在出现意外异常时也能进行必要的清理。

  3. 覆盖返回值:trycatch块中有返回语句时,finally块仍会在方法返回之前执行,甚至有能力修改返回值(但是并不推荐这样,因为会使代码难以理解)。

  4. 明确的意图: 使用finally可以明确表示某段代码无论发生何种情况都必须执行,这对于代码的可读性和维护性是有好处的。它让其他开发者清楚地知道,无论try块中发生了什么,finally块中的代码都是必须执行的。

3、异常的处理流程

3.1、什么是 “调用栈”

方法之间是存在相互调用关系的,这种调用关系我们可以用 “调用栈” 来描述。 在 JVM 中有一块内存空间称为"虚拟机栈" 专门存储方法之间的调用关系。当代码中出现异常的时候,我们就可以使用 e.printStackTrace(); 的方式查看出现异常代码的调用栈。这个调用栈追踪显示了异常发生时的方法调用顺序,从最近的方法调用开始,一直追溯到异常被抛出的源头。调用栈追踪提供的信息通常包括:

  1. 异常类型和描述信息。
  2. 异常发生的代码位置,包括类名、文件名和行号。
  3. 方法调用序列,从发生异常的方法开始,一直到程序入口。
public class demo1 {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]);}
}

在这里插入图片描述
如果向上一直传递都没有合适的方法处理异常, 最终就会交给 JVM 处理,程序就会异常终止(和我们最开始未使用 try catch 时是一样的)。

3.2、异常处理流程总结

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

三、自定义异常类

自定义异常是一种用户定义的异常,它可以帮助处理程序特定的错误情况。创建自定义异常通常有助于更清晰地表达程序的意图和处理程序的特定错误。

自定义异常通常通过继承Java的Exception类或其子类来创建。如果希望自定义异常是受检异常(即必须显式处理的异常),则应继承Exception类。如果希望它是非受检异常,则应继承RuntimeException。自定义异常类应该至少提供一个构造方法,它通常会调用父类的构造方法来初始化异常消息。可以根据需要提供多个构造方法,以便在抛出异常时可以传递不同类型的信息。

public class LogIn {private String userName = "admin";private String password = "123456";public static void loginInfo(String userName, String password) {if (!userName.equals(userName)) {}if (!password.equals(password)) {}System.out.println("登陆成功");}public static void main(String[] args) {loginInfo("admin", "123456");}
}

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

具体方式:

  1. 自定义异常类,然后继承自Exception 或者 RunTimeException
  2. 实现一个带有String类型参数的构造方法,参数含义:出现异常的原因
class UserNameException extends Exception {public UserNameException(String message) {super(message);}
}
class PasswordException extends Exception {public PasswordException(String message) {super(message);}
}

此时我们的 login 代码可以改成:

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

注意:

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/604893.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

leaflet:加载本地shp文件,并在地图上显示出来 (138)

第138个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+leaflet中加载本地shp文件,利用shapefile读取shp数据,转换为json,利用L.geoJSON()在地图上显示图形。 直接复制下面的 vue+openlayers源代码,操作2分钟即可运行实现效果 文章目录 示例效果安装加载shapefile.j…

Docker使用扩展

日升时奋斗&#xff0c;日落时自省 目录 1、容器 1.1、容器的生命周期 1.1.1、容器OOM 1.1.2、容器异常退出 1.1.3、容器暂停 1.2、容器命令 1.2.1、创建容器 1.2.2、启动容器 1.2.3、容器日志 1.2.4、容器交互 1.2.5、容器停止 1.2.6、扩展 1.3、综合演示 2、存…

ChatGPT | 模型架构 | 应用 | 思考

介绍 ChatGPT 3.5 是 OpenAI 推出的语言模型的一个版本&#xff0c;是 GPT&#xff08;生成式预训练模型&#xff09;系列的一部分。在自然语言理解和生成方面具有强大的能力&#xff0c;可以应用于问答系统、文本生成、翻译和对话系统等多个领域。 模型架构 GPT-3.5&#x…

【代码】Keras3.0:实现残差连接

简介 残差连接是一种非常重要的网络结构创新&#xff0c;最早被广泛应用于ResNet&#xff08;Residual Neural Network&#xff09;模型中&#xff0c;由何凯明等人在2015年的论文"Deep Residual Learning for Image Recognition"中提出。 核心思想 通过引入“short…

Linux stm32串口下载程序

一、工具 使用stm32flash进行串口下载 二、stm32flash安装 sudo apt-get install stm32flash 三、查看串口设备名称 先拔掉串口运行下面指令&#xff0c;获得所有设备名称,插上串口再运行一次&#xff0c;新增的就是串口设备名称&#xff0c;记住串口设备名称&#xff0c;以…

【Hadoop】说下HDFS读文件和写文件的底层原理?

文件读取文件的写入 文件读取 客户端调用 FileSystem 对象的 open&#xff08;&#xff09;函数&#xff0c;打开想要读取的文件。其中FileSystem 是 DistributeFileSystem 的一个实例&#xff1b;DistributedFileSystem 通过使用 RPC&#xff08;远程过程调用&#xff09; 访N…

Unity 了解Input Manage下默认的输入轴

在Unity菜单Edit->Project Settings->Input Manager->Axes下有一些默认的输入轴&#xff0c;如 这些输入轴代表不同类型的输入&#xff0c;其中&#xff1a; Horizontal&#xff1a;水平移动输入轴。通常与键盘的左右箭头键、A和D键、游戏手柄的左摇杆水平轴等相关联…

【AI视野·今日CV 计算机视觉论文速览 第282期】Wed, 3 Jan 2024

AI视野今日CS.CV 计算机视觉论文速览 Wed, 3 Jan 2024 Totally 70 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Computer Vision Papers Street Gaussians for Modeling Dynamic Urban Scenes Authors Yunzhi Yan, Haotong Lin, Chenxu Zhou, Weijie Wang, Haiya…

nvm安装教程,实现node的多版本管理(图文界面)

目录 前言1. 安装配置2. 使用方式 前言 由于前端项目不同的node版本&#xff0c;导致重复的卸载安装会比较麻烦&#xff0c;对此需要nvm来管理 类似python版本的差异&#xff0c;可以使用虚拟环境管理&#xff08;anconda&#xff09;&#xff0c;在我原先的文章也有讲解过 …

从法律和经济学角度看工业数据共享

文章目录 前言一、工业数据利用现状二、工业数据的新特点三、数据再利用和储备的现状(一)防止先行者通过数据进行垄断(二)数据储备情况以及数据成本、数量和价值四、工业数据共享的法律经济学模型分析情形一:在没有数据共享政策的情况下,尝试给出如下命题。情形二:有数据…

LeetCode-加一(66)

题目描述&#xff1a; 给定一个由整数组成的非空数组所表示的非负整数&#xff0c;在该数的基础上加一。 最高位数字存放在数组的首位&#xff0c; 数组中每个元素只存储单个数字。 你可以假设除了整数 0 之外&#xff0c;这个整数不会以零开头。 思路&#xff1a; 这里主要分…

《路由与交换技术》---简答题

1、什么是STP&#xff1f;解决什么问题&#xff1f; STP代表生成树协议&#xff08;Spanning Tree Protocol&#xff09;。它是用于在计算机网络中解决环路问题的一种协议。 STP的主要目标是消除环路&#xff0c;保持网络的稳定性和可靠性&#xff0c;同时提供冗余路径以实现网…

快速实现产品智能:用 AI 武装你的 API | 开源日报 No.138

openchatai/OpenCopilot Stars: 3.8k License: MIT OpenCopilot 是一个允许你拥有自己产品的 AI 副驾驶员的项目。它集成了产品底层 API&#xff0c;并可以在需要时执行 API 调用。它使用 LLMs 来确定用户请求是否需要调用 API 端点&#xff0c;然后决定调用哪个端点并根据给定…

【办公软件】手机当电脑摄像头Iriun Webcam软件安装与试用

家里电脑是台式的没有摄像头&#xff0c;但老安卓手机有一台。本来想用小爱摄像头做电脑摄像头&#xff0c;但是发现像素有点差&#xff0c;捣鼓了半天没成功。看网上别人都用旧手机来当电脑摄像头&#xff0c;并且也能使用音频&#xff0c;所以还是用旧手机做摄像头比较香。 …

爆肝整理,接口测试+为什么要做接口测试总结,策底贯通...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、什么是接口测试…

创建Vue3项目

介绍 使用命令创建vue3项目 示例 第一步&#xff1a;执行创建项目命令 npm create vuelatest第二步&#xff1a;填写输入项 第三步&#xff1a;进入study-front-vue3文件夹 cd study-front-vue3第四步&#xff1a;执行npm命令安装依赖 npm install第五步&#xff1a;运行…

YOLO蒸馏原理篇之---MGD、CWD蒸馏

一、MGD蒸馏 论文地址:https://arxiv.org/abs/2205.01529 论文翻译:https://mp.weixin.qq.com/s/FSvo3ns2maTpiTTWsE91kQ 1.1 摘要 知识蒸馏已成功应用于各种任务。当前的蒸馏算法通常通过模仿教师的输出来提高学生的表现。本文表明,教师还可以通过指导学生的特征恢复来提…

60.网游逆向分析与插件开发-游戏增加自动化助手接口-游戏公告功能的逆向分析与测试

内容来源于&#xff1a;易道云信息技术研究院VIP课 上一个内容&#xff1a;文字资源读取类的C还原-CSDN博客 码云地址&#xff08;master分支&#xff09;&#xff1a;https://gitee.com/dye_your_fingers/sro_-ex.git 码云版本号&#xff1a;878db7708de09b448010ef54526fe…

爬虫-3-模拟登录,代理ip,json模块

#本文仅供学习使用(O&#xff40;) 如果服务器响应的数据为json数据: 那么我们可以用 res.json() 或 json模块(将json字符串转换为Python里面的字典类型) 接收数据。

高德地图Web服务使用方法——电子围栏

1 高德地图Web服务 1.1 添加Key 注册高德地图&#xff0c;进入控制台&#xff0c;创建新应用&#xff0c;添加Key&#xff0c;选择Web服务&#xff0c;不添加域名白名单&#xff0c;勾选同意政策。 刷新界面&#xff0c;记住获取到的Key。 1.2 下载安装Postman https://www…