当前位置: 首页 > news >正文

小白如何认识并处理Java异常?

目录

1.异常的概念和为什么要有处理异常

2.异常的体系结构 

 3.异常的分类

 3.1  受检查异常(Checked Exceptions)

3.2  非受检查异常(Unchecked Exceptions)

4.异常的处理方式

5.异常处理的关键字

5.1 异常的抛出 

5.2 异常的声明 

5.3 异常的捕获和处理 

5.4 finally 

5.5 throw 和 throws 的区别

 6.异常处理流程小结

 7.实现自定义异常类


1.异常的概念和为什么要有处理异常

Java异常(Exception)是程序运行时发生的不正常情况,它会中断正常的指令流。异常机制是Java处理程序错误的标准方式,提供了一种结构化的、面向对象的方法来处理运行时问题。 

正常的书面概念可能有点晦涩难懂,在这里可以想象一下:你通过手机APP点外卖的整个过程,正常的流程(没有异常)是这样的:

但是如果这个过程中某个环节出现意外,将导致你收不到外卖。这个过程中的各种意外情况就相当于程序中的"异常"。 

当有这些异常之后,就要对其进行处理,为什么需要这些"异常处理"?

  1. 提前预防:就像APP会先检查你的网络连接再让你下单

  2. 优雅降级:当首选餐厅关门时,APP不会直接崩溃,而是推荐替代选项

  3. 明确反馈:不是简单显示"操作失败",而是告诉你具体原因(余额不足/商家打烊等)

  4. 资源清理:就像最终都会发送订单确认,确保流程完整

  5. 流程中断:当严重错误发生时(如支付失败),立即停止后续操作(不制作/不配送)

Java语言也是类似的,当编译器抛出一个异常时,就要对其进行有效处理。因此,认识Java的异常是非常有必要的,这样才能对异常对症下药。

2.异常的体系结构 

下图是异常的体系结构重要的一部分,包括但不限于图中所有: 

 上图中可知:

  1. Throwable:是异常体系的顶层类,派生出Error和Exception这两个重要子类
  2. Error(错误):是指 Java 虚拟机无法解决的问题,比如 JVM 的内部错误等,典型的有OutOfMemoryError  StackOverflowError
  3. Exception(异常):当发生Exception时,程序🐒可以通过对代码进行处理,使其正常运行

 3.异常的分类

异常有时候在编译时发生,有时候在程序运行时发生,前者称为受检查异常(Checked Exceptions),后者称为非受检查异常(Unchecked Exceptions)。

 3.1  受检查异常(Checked Exceptions)

在程序编译时发生的异常,称为编译时异常,也称为受检查异常(Checked Exceptions)

常见的受检查异常:

( 1 ) IOException :

  • FileNotFoundException:尝试打开不存在的文件

结果显示,受检查异常在编写代码时编译器就已经给出提示(FileReader下方标红),如果不对其进行处理,当运行时就会抛出异常:FileNotFoundException

( 2 )SQLException:数据库操作异常

( 3 )ClassNotFoundException:类加载失败 

3.2  非受检查异常(Unchecked Exceptions)

在编译时不发生,在程序运行时发生的异常,称为运行时异常,也成为非受检查异常(Unchecked Exceptions) 。

常见的非受检查异常:

( 1 )ArithmeticException算术异常

结果显示,非受检查异常在编写代码是编译器不会给出标红提示,但是当运行时就会抛出异常:ArithmeticException,并且给出异常的位置和异常的原因(这里是0不能作为除数)。

( 2 )ArrayIndexOutOfBoundsException数组越界异常

 ( 3 )NullPointerException空指针异常

( 4 )ClassCastException类型转换异常 

4.异常的处理方式

认识异常之后,就先对异常进行处理。处理异常的主要方式有:

( 1 )LBYL(Look Before  You Leap):在操作之前就做充分的检查,即事前防御型

// 典型LBYL代码结构
if (file.exists()) {          // 先检查if (file.canRead()) {     // 再检查// 最后执行操作String content = Files.readString(file.toPath());}
}

 这种方式在在执行操作前显式检查所有可能出错的条件,正常流程和错误处理流程的代码混在一起,使得代码整体会显得比较混乱。因此处理异常时通常使用第二种方式:

( 2 )EAFP(Easier to Ask for Forgiveness than Permission):先执行操作,再处理可能的异常,而不是提前检查所有可能的错误条件,即事后认错型

try {// 可能抛出异常的操作
} catch (可以出现的哪类异常  e) {// 异常处理 
} finally {// 可选的清理代码(无论是否异常都会执行)
}

 这种方式的优势在于正常流程和错误流程是分开的,程序🐒编写代码时更关注正常流程,代码也更清晰。处理异常的核心思想便是EAFP

要真正理解异常,就要认识异常处理的5个关键字: throwtrycatchfinallythrows

5.异常处理的关键字

5.1 异常的抛出 

在编写程序时,如果程序中出现错误,这个时候就需要把错误的信息告诉调用者。在Java中,使用关键字 throw ,抛出一个指定的异常对象,把错误的信息告诉调用者。

语法形式:

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

 如:

public class Main {public static int div(int x , int y) {if (0 == y) {throw new ArithmeticException("除数不能为0!!!");}System.out.println("你错了吗?");return x / y;}public static void main(String[] args) {int a = 666;int b = 0;System.out.println(div(a , b));}
}

 这是一个实现两个数相除的方法,并通过 throw 抛出异常,来看​​​​​​运行结果:

结果显示, 这次给出的错误原因是程序🐒自己编写的,也就是“除数不能为0!!!”,并且可以看到“你错了吗?”并没有打印出来。

注意事项:

  1. throw 必须写在方法体内部
  2. 抛出的对象必须是Exception 或者 Exception 的子类对象
  3. 异常一旦抛出,后面的代码将不再执行 

5.2 异常的声明 

当编写代码时不想处理异常,可以借助关键字 throws 来声明异常,将异常抛给方法的调用者来处理,放在方法的参数列表之后。

语法形式:

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


public class Main {public static int getNum(int[] array , int index) throws NullPointerException,ArrayIndexOutOfBoundsException {if (null == array) {throw new NullPointerException("这个数组是空的!!!");}if (index < 0 || index >= array.length) {throw new ArrayIndexOutOfBoundsException("超出数组边界!!!");}return array[index];}public static void main(String[] args) {int[] array = {0 , 1 , 2 , 3 , 4};System.out.println(getNum(array ,6));}
}

在这个代码中,需要得到下标为index的数组元素,在写方法时通过关键字 throws 声明两个异常,即 NullPointerExceptionArrayIndexOutOfBoundsException,来看运行结果:

结果显示,异常“超出数组边界 ”。

注意事项: 

  1. throws 跟在方法的参数列表之后 
  2. 声明的异常需要是 Exception 或者Exception 的子类
  3. 如果会抛出多个异常,throws 之后要跟多个异常类型,并且使用英文逗号隔开

5.3 异常的捕获和处理 

 throw 是抛出异常,throws 是声明异常,这两个关键字都没有对异常进行真正的处理,而是将异常原因抛出告诉给方法的调用者,再由调用者进行处理。如果要对异常进行真正的处理,就需要使用关键字 try catch

语法形式:

try {//可能出现异常的代码
} catch (捕获的异常类型 e) {//如果抛出异常,在这里进行处理,处理完成后,跳出try-catch结构,继续执行后续代码
}finally {//这里的代码一定会被执行,后面继续介绍
}
//后续代码,如果没有异常或者是异常被处理后,执行这里的代码,如果有异常但没有处理,这里就不会被执行
public class Main {public static void getNum(int[] array, int index) {try {System.out.println("异常之前的代码执行吗???");System.out.println("下标为" + index + "的元素是:" + array[index]);System.out.println("异常之后的代码执行吗????");} catch (NullPointerException e) {System.out.println("数组不能为null !!!");} catch (ArrayIndexOutOfBoundsException e) {System.out.println("索引越界!!!");}System.out.println("try-catch之后的代码执行了...");}public static void main(String[] args) {int[] array = null;getNum(array, 6); // 测试null数组System.out.println("--------分割线--------");array = new int[]{1, 2, 3};getNum(array, 6); // 测试越界索引}
}

在这个代码中,通过 catch 对异常进行处理,来看运行结果:

结果显示,捕获后的异常的 try-catch结构外的代码都被执行了。

注意事项: 

  1. try  代码块中,如果有代码出现异常,那么这个异常代码之后的代码将不再执行
  2. 如果抛出异常类型与 catch 的异常类型不匹配,也就是说异常没有被成功捕获,那么就不会被处理,就会往外抛出异常
  3. try 中可能会有多个不同对象的异常,此时需要多个 catch 进行捕获

5.4 finally 

在编写程序代码时,有一些特点的代码不论是否发生异常,都需要执行,比如程序中打开的资源如数据库链接、IO流,在程序正常或者退出时也要对资源进行回收,此时就需要关键字 finally 来构造一个代码块让这些代码执行起来。

前面说到 try-catch 时,语法形式是这样的:

语法形式:

try {//可能出现异常的代码
} catch (捕获的异常类型 e) {//如果抛出异常,在这里进行处理,处理完成后,跳出try-catch结构,继续执行后续代码
}finally {//这里的代码一定会被执行
}
//后续代码,如果没有异常或者是异常被处理后,执行这里的代码,如果有异常但没有处理,这里就不会被执行

 虽然在前面的结果中看到 try-catch 后续代码依然执行,但那是在处理异常之后的,如果没有对异常进行处理,那么 try-catch 后续代码是不会执行的,此时就突出了 finally 的重要性。

public class Main {public static void getNum(int[] array, int index) {try {System.out.println("异常之前的代码执行吗???");System.out.println("下标为" + index + "的元素是:" + array[index]);System.out.println("异常之后的代码执行吗????");} catch (NullPointerException e) {System.out.println("数组不能为null !!!");}//没有处理索引越界的异常处理finally {System.out.println("finally里面的代码块被执行了...");}System.out.println("try-catch之后的代码执行了...");}public static void main(String[] args) {int[] array = null;getNum(array, 6); // 测试null数组System.out.println("--------分割线--------");array = new int[]{1, 2, 3};getNum(array, 6); // 测试越界索引}
}

 这个代码对数组索引超出边界时并没有对其进行异常处理,来看运行结果:

 结果显示,不论异常是否处理,finally 的代码块一定会被执行!

5.5 throw 和 throws 的区别

 异常处理的5个关键字throwtrycatchfinallythrows其中throw 和 throws有什么区别呢?

(1)throw

作用主动抛出一个异常对象(在方法内部使用)
特点

  • 用于方法内部,当检测到错误条件时立即抛出异常

  • 后面跟一个异常对象实例new ExceptionType()

  • 执行到 throw 时,当前方法立即停止,异常交给调用栈处理

(2)throws

作用声明方法可能抛出的异常类型(在方法签名中使用)
特点

  • 用于方法声明处,告诉调用者该方法可能抛出哪些受检异常(Checked Exceptions)

  • 后面跟异常类名(多个异常用英文逗号分隔)

  • 不实际抛出异常,只是声明可能性

 6.异常处理流程小结

  1. try 代码块最先执行
  2. 如果 try 代码块中没有出现异常,其代码块内的所有代码都会被执行,如何跳过 catch 的代码块;如果 try 代码块中的某一代码出现异常,则 该代码之后的 try 代码块将不会再执行,观察该异常是否和 catch 的异常是否匹配
  3. 如果找到匹配的异常类型,就会执行 catch 代码块中的代码
  4. 如果没有找到匹配的异常类型,就会将异常向上传递给上层调用者,如果向上一直传递没有合适的方法异常处理,最终会交给 JVM 处理,直到程序异常终止(和为会异常处理相似,直接抛出异常,有编译器给出提示,如果算术异常中“ by zero”)

 7.实现自定义异常类

//自定义用户名异常类
public class NameException extends RuntimeException {public NameException(String message) {super(message);}
}
//自定义密码异常类
public class PassWordException extends RuntimeException {public PassWordException(String message) {super(message);}
}
// 用户登录类,封装登录逻辑
class UserLogin {// 用户属性private String name;// 用户名private String password;// 密码// Getter和Setter方法public String getName() {return name;}public void setName(String name) {this.name = name;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}/*** 用户登录验证方法* @param name 输入的用户名* @param password 输入的密码* @throws NameException 当用户名不匹配时抛出* @throws PassWordException 当密码不匹配时抛出*/public void userLogin(String name , String password) throws NameException, PassWordException {// 验证用户名if (! this.name.equals(name)) {throw new NameException("输入的用户名错误异常!");}// 验证密码if (! this.password.equals(password)) {throw new PassWordException("输入的密码错误异常!");}System.out.println("登陆成功");   // 验证通过}
}// 主程序类
public class Main {public static void main(String[] args) {//实现一个简单的控制台版用户登陆程序, 程序启动提示用户输入用户名密码. 如果用户名密码出错, 使用自定义异常的方式来处理UserLogin userLogin = new UserLogin();// 创建用户登录对象// 设置正确的用户名和密码(模拟数据库中的用户数据)userLogin.setName("zhangsan");userLogin.setPassword("12345");try {// 尝试登录(这里可以替换为从控制台获取的用户输入)userLogin.userLogin("zhangsan" , "12345");} catch (NameException e) {// 捕获并处理用户名异常e.printStackTrace();} catch (PassWordException e) {// 捕获并处理密码异常e.printStackTrace();}}
}

 注意事项:

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

http://www.lryc.cn/news/599868.html

相关文章:

  • 【嵌入式汇编基础】-ARM架构基础(二)
  • 从0到1:初创企业适合做企业架构吗?TOGAF 能带来什么?
  • 小架构step系列25:错误码
  • Haproxy七层代理及配置
  • 数据结构2-集合类ArrayList与洗牌算法
  • 在Word和WPS文字中添加的拼音放到文字右边
  • JS与Go:编程语言双星的碰撞与共生
  • 初识opencv04——图像预处理3
  • ModelWhale+数据分析 消费者行为数据分析实战
  • 判断子序列-leetcode
  • 广州 VR 安全用电技术:工作原理、特性及优势探析​
  • CTF-Web题解:“require_once(‘flag.php‘); assert(“$i == $u“);”
  • Linux系统基本配置以及认识文件作用
  • 双非上岸985!专业课140分经验!信号与系统考研专业课140+上岸中南大学,通信考研小马哥
  • 20分钟学会TypeScript
  • 本地内网IP映射到公网访问如何实现?内网端口映射外网工具有哪些?
  • VUE2 学习笔记6 vue数据监测原理
  • 局域网 IP地址
  • Linux tcpdump 抓取udp 报文
  • 开源语音TTS与ASR大模型选型指南(2025最新版)(疯聊AI提供)
  • 动态规划:从入门到精通
  • 中国开源Qwen3 Coder与Kimi K2哪个最适合编程
  • 电子电子架构 --- 软件项目的开端:裁剪
  • 【IDEA】IDEA中如何通过分支/master提交git?
  • Hadoop 之 Yarn
  • 【软件工程】构建软件合规防护网:双阶段检查机制的实践之道
  • 【AJAX】Promise详解
  • HashMap的线程安全性 vs ConcurrentHashMap
  • 机器学习中knn的详细知识点
  • 基于springboot的候鸟监测管理系统