【JavaSE】为什么需要异常处理机制?Java异常详解 ———18张图,近万字带你彻底搞懂异常
💁 个人主页:Nezuko627的博客主页
❤️ 支持我:👍 点赞 🌷 收藏 🤘关注
🎏 格言:立志做一个有思想的程序员 🌟
📫 作者介绍:本人本科软件工程在读,博客主要涉及JavaSE、JavaEE、MySQL、SpringBoot、算法等知识。专栏内容长期更新,如有错误,欢迎评论区或者私信指正!感谢大家的支持~~~
本篇学习目标:
- ⭐️ 理解为什么需要异常处理机制;
- ⭐️ 了解什么是异常,及编译时异常与运行时异常的区别;
- ⭐️ 掌握常见的几种运行时异常的处理;
- ⭐️ 熟练使用 try-catch-finally 以及 throws 的方式处理异常;
- ⭐️ 知道 throws 与 throw 的区别;
- ⭐️ 了解自定义异常,并懂得基本的使用;
本文来自专栏:JavaSE系列专题知识及项目 欢迎点击支持订阅专栏 ❤️
文章目录
- 1 异常引入
- 2 异常简介
-
- 2.1 异常的概念
- 2.2 常见异常体系图
- 3 常见运行时异常举例
-
- 3.1 NullPointerException
- 3.2 ArrayIndexOutOfBoundsException
- 3.3 ClassCastException
- 3.4 NumberFormatException
- 4 异常处理
-
- 4.1 try-catch-finally 异常处理
- 4.2 try - catch练习题(坑)
-
- 4.2.1 阅读程序
- 4.2.2 输入校验
- 4.3 throws 异常处理
- 4.4 自定义异常
-
- 4.4.1 自定义异常实例
- 4.5 throw 和 throws 的区别
- 5 编程练习
- 写在最后
1 异常引入
🌔 不知道初学Java 时,大家是不是和博主一样一脸懵:“这异常到底是个什么东东,有什么用嘞?” 今天,咱们就专门来展开讲讲异常。
还是那个教科书里面的标准例子,来看一下吧:
在例子中,我们将 0 作为除数进行运算,运行结果如下:
😢 程序控制台好像出现了不得了的东西!细细一看,原来是告诉我们出现了算术异常。不过只要再细心看一下代码,你就会发现:程序并没有执行完成,因为控制台并没有输出“程序运行结束标记”这句话!而是在计算 res 的时候,就中断了。 这就导致了一个很严重的问题:在我们实际开发中,一个完整的项目,文件可能都多达上千个,难道可以因为这样的小错误就直接崩溃,停止运行吗?
答案是否定的,因此, 我们需要使用异常处理机制,来提升项目的健壮性!
🍎Tips: 在idea编译器中,我们可以先选中代码块,然后使用快捷键 ctrl + alt + t
在弹出的二级菜单中,一键添加 try - catch
,(偷懒小技巧增加了~~~)
来看一下添加异常机制后,程序的运行情况吧!
🐜 代码如下:
🍓 运行结果:
🐘 虽然运行结果同样抛出了异常信息,但是,程序并没有因此而中止!而是继续向后执行,结束标记输出就是最好的证明~
怎么样?现在是不是就对异常有兴趣啦!来,进入正题,我们慢慢聊~
2 异常简介
2.1 异常的概念
在 Java 中,异常即为程序执行过程中发生的不正常情况。 但是需要注意的是,程序员在编码过程中的逻辑与语法错误不属于异常的范畴!
🍓 程序执行过程中所发生的异常可以分为如下两大类:
1️⃣ Error
: Java 虚拟机无法解决的严重问题。如: JVM系统内部错误、资源耗尽等。举例:栈溢出(StackOverflowError)
2️⃣ Exception
: 其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。分为两大类:
- 运行时异常: 又叫非受检异常,即程序运行时,发生的异常;
- 编译时异常: 又叫受检异常,即编程时,编译器检查出的异常。
图示如下:
🖊 助记:
编译时异常: 编译器检查出来的异常,必须处理,不然运行不了;
运行时异常: 编译器没有检查出来,程序可以跑,可以 选择性处理。
2.2 常见异常体系图
Java把异常当作对象来处理,并定义一个基类java.lang.Throwable
作为所有异常的超类。在Java API中已经定义了许多异常类,这些异常类分为两大类,错误Error
和异常Exception
。Java异常体系结构呈树状,常见异常的层次结构图如图所示:
📖 Throwable
实现了 Serializable
接口。向下分为 Error
与 Exception
。而 Exception
我们可以通过异常处理机制来解决。我们着重处理 RuntimeException
即 运行时异常,其常见的子类如下,上面的图可能看不清,我们来放大看:
3 常见运行时异常举例
3.1 NullPointerException
空指针异常,当应用程序在需要对象的地方使用null时,抛出该异常。
🐜 示例代码:
public class NullPointerException { public static void main(String[] args) { String s = null; System.out.println(s.length()); }}
🍓 结果:
Exception in thread “main” java.lang.NullPointerException: Cannot invoke “String.length()” because “s” is null
at exception.NullPointerException.main(NullPointerException.java:10)
3.2 ArrayIndexOutOfBoundsException
数组下标越界异常。 用非法索引访问数组时抛出该异常,即数组越界。
🐜 示例代码:
public class ArrayIndexOutOfBoundsException { public static void main(String[] args) { int[] arr = {1, 2, 3}; System.out.println(arr[3]); // 越界 }}
🍓 结果:
Exception in thread “main” java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
at exception.ArrayIndexOutOfBoundsException.main(ArrayIndexOutOfBoundsException.java:10)
3.3 ClassCastException
类型转换异常。当试图将一个对象强制转换为不是实例的子类时,抛出该异常。
🐜 示例代码:
public class ClassCastException { public static void main(String[] args) { Animal dog = new Dog(); Dog d = (Dog)dog; // 向下转型 Cat c = (Cat)dog; // 该句错误!!!! }}class Animal{}class Dog extends Animal{}class Cat extends Animal{}
🍓 结果:
Exception in thread “main” java.lang.ClassCastException: class exception.Dog cannot be cast to class exception.Cat (exception.Dog and exception.Cat are in unnamed module of loader ‘app’)
at exception.ClassCastException.main(ClassCastException.java:11)
3.4 NumberFormatException
数字格式异常。当应用程序试图将字符串转成一种数值类型,但字符串不能转换为适当格式时,抛出该异常。 使用异常我们可以确保输入满足条件的数字。
🐜 示例代码:
public class NumberFormatException { public static void main(String[] args) { int num = Integer.parseInt("123.456"); // 转不了 }}
🍓 结果:
Exception in thread “main” java.lang.NumberFormatException: For input string: “123.456”
at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:68)
at java.base/java.lang.Integer.parseInt(Integer.java:652)
at java.base/java.lang.Integer.parseInt(Integer.java:770)
at exception.NumberFormatException.main(NumberFormatException.java:9)
4 异常处理
异常处理即当异常发生时,对异常处理的方式。 主要有两种处理方式:
try-catch-finally
:程序员在代码中自行处理,捕获异常;throws
:将异常抛出,交给调用者(方法)来处理,最顶级的处理者就是JVM。示意图如下:
main方法如果没有进行 try catch,默认会进行 throws。
4.1 try-catch-finally 异常处理
Java 提供 try
和 catch
块来处理异常。try
块用于包含可能出错的代码,catch
块用于处理 try
块发生的异常。finally
块可选择性添加,用于执行始终需要执行的代码。
🅰️ 基本语法:
try{// 可能发生异常的代码// 将异常生成对应的异常对象,传递给 catch 块}catch(异常){// 对异常的处理}finally{// 不管 try 是否有异常,始终都要执行 finally 块的代码// 通常将需要释放资源的代码放在 finally}
🍓 使用细节:
1️⃣ 如果异常发生了,则异常后面的代码不会执行,直接进入到 catch
块。
2️⃣ 如果异常没有发生,则顺序执行 try
代码块,不会进入 catch
块。
3️⃣ 如果 try
代码块有多个异常,可以使用多个 catch
分别捕获并进行相应的处理。 但是要求 子类必须在父类前面! 值得注意的是,异常捕获的时候,当捕获到第一个,后面的将不再捕获。
4️⃣ 允许使用 try - finally
,但是这种做法相当于没有捕获异常,finally
相当于进行了善后处理, 即 程序还是会因为异常中断,但是会执行该代码块中的代码后再中断(垂死挣扎)
4.2 try - catch练习题(坑)
4.2.1 阅读程序
🐜请阅读下面代码,猜猜输出结果:
public class Exception01 { public static int method(){ int i = 1; try { i++; String[] name = new String[3]; if(name[1].equals("ABC")){ // 发生了空指针异常 System.out.println("ABC"); } }catch (NullPointerException e){ return ++i; }finally { ++i; System.out.println("i = " + i); } return 0; } public static void main(String[] args) { System.out.println(method()); }}
🍎 结果:
i = 4
3
🍉 解析:在 if 语句中发生了空指针异常,try 块其余代码不执行。执行 catch 块代码,此时应该 return 3。但是由于 finally
块一定要被执行,因此,程序会先将 return 的 i 值暂时存储起来,去执行 finally 块的代码。所以先输出 i = 4 后返回3.
4.2.2 输入校验
🐜 题目要求:
请你编写一段程序,用于判断用户的输入:如果输入不是整数,就提示错误信息,直到输入正确为止。
🐟 思路分析:
- 创建 Scanner 对象;
- 使用循环去接收一个输入;
- 将该输入尝试转换成 int;
- 如果转换时发生异常,说明输入有误,不能转换;
- 如果没有抛出异常,则输入正确,提示即可。
🍓 参考代码:
public class TryCatch { public static void method() { Scanner scanner = new Scanner(System.in); int num = 0; while (true) { System.out.println("请输入一个整数: "); try { num = Integer.parseInt(scanner.next()); // 有可能抛出异常 break; // 没有抛出则退出循环 } catch (NumberFormatException e) { System.out.println("你输入的不是整数, 请重新输入!"); } } System.out.println("输入正确!"); } public static void main(String[] args) { method(); }}
🍎 实验结果:
4.3 throws 异常处理
🆔 基本介绍:
如果一个方法可能生成某种异常,但是 并不确定如何处理这些异常,则该方法应显式地声明抛出异常 ,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理。在方法声明中抛出异常的列表,throws
后面的异常类型可以是方法中产生的异常类型,也可以是它的父类。
🅰️ 基本语法:
方法名 throws 异常类1, 异常类2......{// 方法体}
🍓 使用细节:
1️⃣ 对于运行时异常,程序如果没有处理,默认就是 throws
的方式处理;
2️⃣ 子类重写父类的方法时,对抛出异常有一条规定:子类重写的方法,所抛出的异常类型要么与父类抛出的异常一致,要么为父类抛出的异常类型的子类型; 演示如下:
4.4 自定义异常
当程序出现了某些错误,但该错误信息并没有在 Throwable
子类中描述处理,这个时候可以自己设计异常类,用于描述该错误信息。
🐱 自定义异常的步骤:
🐈 1.定义类:自定义异常类名,继承
Exception
或者RuntimeException
;
🐈 2. 如果继承了 Exception,属于编译异常;
🐈 3.如果继承了 RuntimeException,属于运行异常。
4.4.1 自定义异常实例
🐜来看一个需求:
当接收 Person 对象年龄时,要求范围在 18~120 之间,否则抛出自定义异常,并给出提示信息。(要求继承 RuntimeException)
🅰️ 参考代码:
public class Main { public static void main(String[] args) { Person person = new Person(125); // 设置年龄125 出现了异常 }}class Person{ private int age; public Person(int age){ this.age = age; ageException(); } // 判断异常 public void ageException(){ if(!(age >= 18 && age <= 120)){ throw new AgeException("年龄需要在18 ~ 120之间"); } System.out.println("范围正常"); }}// 自定义异常class AgeException extends RuntimeException{ public AgeException(String message){ super(message); }}
🍎 结果:
4.5 throw 和 throws 的区别
🐜 区别一览表:
关键字 | 意义 | 位置 | 作用(关键字后面的跟屁虫) |
---|---|---|---|
throws | 异常处理的一种方式 | 方法声明处 | 异常类型 |
throw | 手动生成异常对象的关键字 | 方法体中 | 异常对象 |
5 编程练习
🍉 题目描述(题目摘自 bilibili 韩顺平 Java 课程 P456):
编写程序接收两个参数(整数),计算两数相除。要求使用方法 cal(int num1, int num2)。对于数据格式不正确(NumberformatException)、缺少命令行参数(ArrayIndexOutOfBoundsException)、除0进行异常处理(ArithmeticException)。
🐜 参考代码:
public class Main { public static void main(String[] args) { // 先检验输入参数的个数 try { if(args.length != 2){ throw new ArrayIndexOutOfBoundsException("参数个数不对"); } // 把接收到的参数转成整数 int n1 = Integer.parseInt(args[0]); int n2 = Integer.parseInt(args[1]); double res = cal(n1, n2); // 可能算数异常 } catch (ArrayIndexOutOfBoundsException e) { System.out.println(e.getMessage()); } catch (NumberFormatException e){ System.out.println("参数格式不正确"); } catch (ArithmeticException e) { System.out.println("算术异常,0不能做除数"); } } // 编写方法,计算 public static double cal(int n1, int n2){ return n1/n2; }}
🐟 输入(配置如下图,注意用空格分隔):
🍎 结果:
算术异常,0不能做除数
写在最后
🌟以上便是本文的全部内容啦,后续内容将会持续免费更新,如果文章对你有所帮助,麻烦动动小手点个赞 + 关注,非常感谢 ❤️ ❤️ ❤️ !
如果有问题,欢迎私信或者评论区!
共勉:“你间歇性的努力和蒙混过日子,都是对之前努力的清零。”