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

news2024/11/14 20:25:32

在这里插入图片描述

  • 👑专栏内容: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.coloradmin.cn/o/1365277.html

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

相关文章

C#中List<T>底层原理剖析

C#中List底层原理剖析 1. 基础用法2. List的Capacity与Count&#xff1a;3.List的底层原理3.1. 构造3.2 Add()接口3.3 Remove()接口3.4 Inster()接口3.5 Clear()接口3.6 Contains()接口3.7 ToArray()接口3.8 Find()接口3.8 Sort()接口 4. 总结5. 参考 1. 基础用法 list.Max() …

2024龙年艺术字矢量Ai设计文件60套

2024新年将至&#xff0c;设计师们早已开始为龙年海报、推文的制作摩拳擦掌。该合集不仅内容丰富多样,作为矢量文件资源&#xff0c;也能够让设计者更为轻松地编辑与创作。 合集内另附200多张电脑壁纸。 文件总大小368MB 链接&#xff1a;https://pan.quark.cn/s/0caab4cf065…

Google Earth Engine谷歌地球引擎GEE批量计算一年中每个指定天数范围内遥感影像平均值的方法

本文介绍在谷歌地球引擎&#xff08;Google Earth Engine&#xff0c;GEE&#xff09;中&#xff0c;计算长时间序列遥感影像数据在多年中&#xff0c;在每一个指定天数的时间范围内的平均值的方法。 本文是谷歌地球引擎&#xff08;Google Earth Engine&#xff0c;GEE&#x…

MySQL BufferPool精讲

缓存的重要性 我们知道&#xff0c;对于使用InnoDB作为存储引擎的表来说&#xff0c;不管是用于存储用户数据的索引&#xff08;包括聚簇索引和二级索引&#xff09;&#xff0c;还是各种系统数据&#xff0c;都是以页的形式存放在表空间中的&#xff0c;而所谓的表空间只不过…

杰发科技AC7840——CAN通信简介(2)

1.时钟频率 2.位时间 3.采样点 4.消息缓冲区 和ST、NXP的邮箱类似&#xff0c;AutoChips用了缓冲区的概念。 5.接收缓冲区 屏蔽掉demo程序的发送&#xff0c;只看接收情况 在回调中接收数据 先判断是不是进了接收中断 接收数据的处理函数 所有buff数据放到Info buff的内容 BUF…

环境中碳循环

含碳的物质有CO2、CO、CH4、糖类、脂肪和蛋白质等&#xff0c;碳循环以CO2为中心&#xff0c;CO2被植物、藻类利用进行光合作用&#xff0c;合成植物性碳&#xff1b;动物摄食植物就将植物性碳转化为动物性碳&#xff1b;动物和人呼吸放出CO2&#xff0c;有机碳化合物被厌氧微生…

AArch64 memory management学习(一)

提示 该博客主要为个人学习&#xff0c;通过阅读官网手册整理而来&#xff08;个人觉得阅读官网的英文文档非常有助于理解各个IP特性&#xff09;。若有不对之处请参考参考文档&#xff0c;以官网参考文档为准。AArch64 memory management学习一共分为两章&#xff0c;这是第一…

Github 2024-01-08开源项目周报 Top14

根据Github Trendings的统计&#xff0c;本周(2024-01-08统计)共有14个项目上榜。根据开发语言中项目的数量&#xff0c;汇总情况如下&#xff1a; 开发语言项目数量Python项目5TypeScript项目3C项目2Dart项目1QML项目1Go项目1Shell项目1Rust项目1JavaScript项目1C#项目1 免费…

【网络安全】PKI加密

1、PKI概述 名称&#xff1a;Public Key Infrastruction 公钥基础设施 作用&#xff1a;通过加密技术和数字签名保证信息的安全 组成&#xff1a;公钥机密技术、数字证书、CA、RA 2、信息安全三要素 机密性 完整性 身份验证/操作的不可否认性 3、哪些IT领域用到PKI&…

【Golang】go编程语言适合哪些项目开发?

文章目录 **前言****Go 编程语言适合哪些项目开发&#xff1f;****1. 网络编程项目&#xff1a;****2. 大数据处理项目&#xff1a;****3. 云计算项目&#xff1a;****4. Web开发项目&#xff1a;****5. 嵌入式系统项目&#xff1a;****6.API开发**:**1. 并发性能&#xff1a;*…

数据库内核那些事|细说PolarDB优化器查询变换:IN-List变换

导读 数据库的查询优化器是整个系统的"大脑"&#xff0c;一条SQL语句执行是否高效在不同的优化决策下可能会产生几个数量级的性能差异&#xff0c;因此优化器也是数据库系统中最为核心的组件和竞争力之一。阿里云瑶池旗下的云原生数据库PolarDB MySQL版作为领先的云…

prometheus 黑盒监控

黑盒监控 “白盒监控” 是需要把对应的Exporter程序安装到被监控的目标主机上&#xff0c;从而实现对主机各种资源以及状态的数据采集工作 ”黑盒监控“ 是不需要把Exporter程序部署到被监控的目标主机上&#xff0c;比如全球的网络质量的稳定性&#xff0c;通常用ping操作&am…

在 Mac 上轻松安装和配置 JMeter

Apache JMeter 是一个开源的负载测试工具&#xff0c;可以用于测试静态和动态资源&#xff0c;确定服务器的性能和稳定性。在本文中&#xff0c;我们将讨论如何下载和安装 JMeter。 安装 Java&#xff08;已安装 Java 的此步骤可跳过&#xff09; 要安装 Java&#xff0c;请按…

基于SSM的停车管理系统设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

1-03C语言超基础语法

一、概述 为了更好的进行后续的课程&#xff0c;避免出现"老师&#xff0c;我还没学过的东西&#xff0c;你怎么直接用&#xff1f;"诸如此类疑问&#xff0c;本小节就诞生了。 实际上&#xff0c;整个第一个大章节的所有小节都是"C语言基础语法"&#x…

ORA-600 adg无法查询故障

再续前缘 ORA-600[12406]故障解决-CSDN博客 当你点背的时候&#xff0c;看似一个简单的case&#xff0c;总是会迎来反转 上次改完参数没两天&#xff0c;又出现了报错不同&#xff0c;但是现象相似的情况 这次是 ORA-600 [kksgaGetNoAlloc_Int0] 这次出现故障的范围更大&a…

XML技术分析05

一、DOM 使用DOM扫描器程序&#xff1a;DOM扫描器是一种非常通用的程序&#xff0c;它不需知道用户定制的XML标记的确切含义。DOMAPI具有某些能把任何数据存储到树形结构中的接口。扫描器具有一组实现了这些接口的类&#xff0c;可以实例化这些类的对象。 这些接口和类…

CAN协议层详细介绍

CAN物理层协议介绍-CSDN博客 目录 1. CAN的波特率及位同步 2. 位时序分解 3. CAN的报文种类及结构 3.1 报文的种类 3.2 数据帧的结构 3.2.1 仲裁段 3.2.2 RTR位(Remote Transmission Request Bit) 3.2.3 IDE位(Identifier Extension Bit) 3.2.3 SRR位(Substi…

K8S--安装MySQL8(单机)

原文网址&#xff1a;K8S--安装MySQL8&#xff08;单机&#xff09;-CSDN博客 简介 本文介绍K8S部署MySQL8&#xff08;单机&#xff09;的方法。 本文的目标 1.通过PV和PVC&#xff08;hostPath方式&#xff09;存储MySQL的数据 2.通过Deployment、Service部署MySQL8&…