异常
1. 异常的引入
案例,运行下面代码,看看有什么问题。
public class Exception01 {
public static void main(String[] args) {
System.out.println("程序开始运行....");
int num1 = 10;
int num2 = 0;
int res = num1 / num2;
System.out.println("程序继续运行....");
}
}
- 代码分析:
0. 输出 程序开始运行…;
1. num1 / num2 => 10 / 0;
2. 当执行到 num1 / num2 因为 num2 = 0, 程序就会出现(抛出)异常 ArithmeticException;
3. 当抛出异常后,程序就退出,崩溃了 , 下面的代码不再执行(不输出程序继续运行…);
4. 大家想想这样的程序好吗? 不好,不应该出现了一个不算致命的问题,就导致整个系统崩溃;
-
java 设计者,提供了一个叫 异常处理机制来解决该问题。如果程序员,认为一段代码可能出现异常/问题,可以使用try-catch异常处理机制来解决,从而保证程序的健壮性。
-
在IDEA 中,将该代码块->选中->快捷键 ctrl + alt + t -> 选中 try-catch; 如果进行异常处理,那么即使出现了异常,程序也可以继续执行下去。
-
代码优化:
public class Exception01 {
public static void main(String[] args) {
System.out.println("程序开始运行....");
int num1 = 10;
int num2 = 0;
try {
int res = num1 / num2;
} catch (Exception e) {
e.printStackTrace();// 这条语句是系统默认有的,可以省略;
}
System.out.println("程序继续运行....");// 可以执行该语句;
}
}
- 可以发现,在加入异常处理机制后,程序会完整执行,在出现异常位置之后的语句也正常输出了;原本系统会报错的部分则会在最后输出。
2. 异常的基本概念
2.1 异常的概念
- 如下图:
- 说明:
(1)区分“异常事件”和“异常”,“异常事件”分为两大类,分别是“错误”(Error)和“异常”(Exception);我们一般说的“异常”指的是 Exception 。
(2)“错误”(Error)是程序员无法通过代码来控制的事情,在本篇不会介绍如何处理错误,而只解释如何处理异常。
(3)“异常”(Exception)又分为两类,一种是编译时异常,一种是运行时异常;这两种异常程序员都可以通过代码来处理。。
(4)编译时异常,即程序在编译时产生的异常;编译器会直接检测出来是否存在编译时异常,出现了编译时异常的程序编译器根本编译不了,程序员必须直接处理,再运行程序。
(5)运行时异常,即程序在运行时才产生的异常;编译器不能检测出来程序是否存在运行时异常,在程序运行时才能知道是否会出现运行时异常;程序员应该尽量避免运行时异常。上面的程序中出现的就是运行时异常。
(6)对于运行时异常,程序员可以不做处理,因为这类异常很普遍,若全部处理可能会对程序的可读性和运行效率产生影响。
2.2 异常的体系图
- 在Java 中,所有可能出现的“异常事件”对应了各个异常事件类,程序员通过调用异常事件类中的方法来查看和解决“异常事件”。
- 异常类体系图如下:
- 异常类体系图说明:
(1)所有“异常事件”类的最高父类是 Throwable 类,它继承了 Object 类。
(2)Error 类对应了“错误”;Exception 类则对应了“异常”,这里只讨论 Exception 类
(3)Exception 类又分成了两类:RuntimeException 类对应了“运行时异常”;需要注意,“编译时异常”则直接继承自 Exception 类,和 RuntimeException 类是同级类。
(4)RuntimeException 类下面则是多个具体的运行时异常的子类;比如我们开篇举例的 num1 / num2 所出现的运行时异常,就是 ArithmicException 类。继承自 RuntimeException 类。
- 下面开始重点讨论 Exception 类的内容。
3. 运行时异常
- 运行时异常,即程序在运行时才产生的异常;编译器不能检测出来程序是否存在运行时异常,在程序运行时才能知道是否会出现运行时异常;程序员应该尽量避免运行时异常。
- 常见的运行时异常包括:
运行时异常类 | 解释 |
---|---|
NullPointerException | 空指针异常 |
ArithmeticException | 数学运算异常 |
ArrayIndexOutOfBoundsException | 数组下标越界异常 |
ClassCastException | 类型转换异常 |
NumberFormatException | 数字格式不正确异常 |
3.1 NullPointerException 空指针异常
-
当应用程序试图在需要对象的地方使用 null 时,抛出该异常。
-
代码说明:
public class NullPointerException_ {
public static void main(String[] args) {
String name = null;
System.out.println(name.length()); // 抛出 NullPointerException
}
}
3.2 ArithmeticException 数学运算异常
- 当出现异常的运算条件时,抛出此异常。例如,用一个整数“除以0”时,抛出该异常。
- 代码说明:
public class ArithmeticException_ {
public static void main(String[] args) {
int num1 = 10;
int num2 = 0;
int res = num1 / num2; // 抛出 ArithmeticException
}
}
3.3 ArrayIndexOutOfBoundsException 数组下标越界异常
- 用非法索引访问数组时抛出的异常;如果索引为负或大于等于数组大小,则该索引为非法索引。
- 代码说明:
public class ArrayIndexOutOfBoundsException_ {
public static void main(String[] args) {
int[] arr = {
1,2,4};
for (int i = 0; i <= arr.length; i++) {
System.out.println(arr[i]);// 这里抛出ArrayIndexOutOfBoundsException
}
}
}
3.4 ClassCastException 类型转换异常
- 当试图将对象强制转换为不是实例的子类时,抛出该异常。
- 代码说明:
public class ClassCastException_ {
public static void main(String[] args) {
A b = new B(); // 向上转型
B b2 = (B)b;// 向下转型,这是正确的
C c2 = (C)b;// 抛出 ClassCastException
}
}
class A {
} // 父类
class B extends A {
} // 子类
class C extends A {
} // 子类
3.5 NumberFormatException 数字格式不正确异常
- 当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常; => 使用给异常我们可以确保输入的是满足条件数字。
- 代码说明:
public class NumberFormatException_ {
public static void main(String[] args) {
String name = "韩顺平教育";
String age = "1234";
// 将String 转成 int
int num1 = Integer.parseInt(age);// 不会抛出异常
System.out.println(num1);
int num2 = Integer.parseInt(name);// 抛出 NumberFormatException
}
}
4. 编译时异常
- 编译时异常,即程序在编译时产生的异常;编译器会直接检测出来是否存在编译时异常,出现了编译时异常的程序编译器根本编译不了,程序员必须直接处理,再运行程序。
- 常见的编译时异常包括:
- 这里以 FileNotFoundException 类举例,其他编译时异常类似。
- 代码举例如下:
public class Exception02 {
public static void main(String[] args) {
try {
FileInputStream fis;
fis = new FileInputStream("d:\\aa.jpg");// 不存在该文件,编译器可以检测到,必须直接处理;
int len;
while ((len = fis.read()) != -1) {
System.out.println(len);
}
fis.close();
} catch (IOException e) {
e.printStackTrace();// 处理该异常,程序可以被编译运行。
}
}
}
5. 异常处理
- 异常处理:就是当异常(Exception)发生时,处理异常的方式。
- 异常处理的两种方式:
(1)try-catch :程序员在程序中捕获发生的异常,直接处理。
(2)throws :不直接处理异常,而是将发生的异常抛出,(一层一层向上)交给调用者(方法)来处理,最顶级的处理者就是JVM 。(看例子理解)
5.1 try-catch 异常处理
- Java 提供try 和 catch 块来直接处理发生的异常。try 块用于包含可能发生异常的代码;catch 块用于处理 try 块中发生的异常;可以根据需要在程序中使用多个 try-catch 块。
5.1.1 try-catch 基本用法
- 基本语法:
try {
// 此处包含可能会发生异常的代码,包括运行时、编译时异常;
// 如果此处的代码块发生了异常,将其生成对应的异常类对象,作为实参传给catch 块;
} catch (Exception e) {
e.printStackTrace();
// Java默认提供的处理异常的语句,可以另外添加其他语句,
// 也可以省略该语句。
} finally {
// 还可以添加 finally 块,finally 块中的语句必须执行。
}
}
- 代码举例:
public class ArithmeticException_ {
public static void main(String[] args) {
int num1 = 10;
int num2 = 0;
try {
int res = num1 / num2; // 生成 ArithmeticException 类对象,传给catch 块
} catch (Exception e) {
e.printStackTrace();
System.out.println("程序出现了异常");// 自定义异常处理方法
}
}
}
5.1.2 try-catch 处理机制示意图
- 如下图:
5.1.3 try-catch 注意事项和细节
(1)在 try 块中,如果某条语句发生了异常,则会直接跳到 catch 块中处理异常,不会再执行该异常语句后的代码;处理完异常后,跳出 catch 块,继续执行程序下面剩余的代码。
(2)如果 try 块中的语句全部没有发生异常,则顺序执行完 try 块中的语句,不会进入 catch 块中。
(3)如果希望无论是否发生异常,都执行某段代码(比如释放资源等),则可以在catch 块后 添加 finally 块,在 fanlly 块中写入始终执行的代码。
- 代码举例:
public class TryCatchDetail {
public static void main(String[] args) {
// 1. 如果异常发生了,则异常发生后面的代码不会执行,直接进入到catch块
// 2. 如果异常没有发生,则顺序执行try的代码块,不会进入到catch
// 3. 如果希望不管是否发生异常,都执行某段代码(比如关闭连接,释放资源等)则使用 finally 块
try {
// 可能会发生异常的代码:
String str = "韩顺平";
int a = Integer.parseInt(str);
System.out.println("数字:" + a);
} catch (NumberFormatException e) {
System.out.println("异常信息=" + e.getMessage());// 处理异常
} finally {
System.out.println("finally代码块被执行...");// 无论是否发生异常,都会执行这段代码;
}
// 在处理完异常后,继续执行程序剩下的代码;
System.out.println("程序继续执行...");
// 如果没有使用 try-catch 处理异常,若发生了异常,则程序剩余的代码不会被执行。
}
}
(4)一段程序可以有多个 catch 块,用于捕获不同的异常类,但要求父类异常必须在后,子类异常在前(比如 Exception 类在后,NullPointerException 类在前);如果发生了子类异常,则只会匹配子类的 catch 块用于处理异常。
- 代码举例:
public class TryCatchDetail02 {
public static void main(String[] args) {
//1.如果try代码块有可能有多个异常
//2.可以使用多个catch 分别捕获不同的异常,相应处理
//3.要求子类异常写在前面,父类异常写在后面
try {
Person person = new Person();
person = null;
System.out.println(person.getName());// 抛出 NullPointerException类异常
int n1 = 10;
int n2 = 0;
int res = n1 / n2;// 抛出 ArithmeticException类异常
} catch (NullPointerException e) {
System.out.println("空指针异常=" + e.getMessage());
} catch (ArithmeticException e) {
System.out.println("算术异常=" + e.getMessage());
} catch (Exception e) {
System.out.println("其他类异常" + e.getMessage());
}
}
}
class Person {
private String name = "jack";
public String getName() {
return name;
}
}
(5)在没有编译时异常发生时,可以进行 try - finally 块配合使用,这种用法相当于没有捕获异常,因此如果发生运行时异常程序会直接中断退出。应用场景:就是执行一段代码,无论是否发生异常,都必须执行某个业务逻辑。
- 代码举例:
public class TryCatchDetail03 {
public static void main(String[] args) {
try{
int n1 = 10;
int n2 = 0;
System.out.println(n1 / n2);// 抛出异常,但没有 catch处理,直接进入 finall块
}finally {
System.out.println("执行了finally..");// 此语句一定会执行
}
// 由于没有处理异常,程序会中断退出,不会执行下面代码
System.out.println("程序继续执行..");
}
}
(6)在 try-catch-finally 块中,如果没有出现异常,则先执行try 块中的所有语句,不执行 catch 块中的语句;最后一定要执行 finally 块中的语句。
(7)在 try-catch-finally 块中,如果出现异常,则 try 块中异常语句后的代码不再执行,直接跳到 catch 块中执行;最后一定还要执行 finally 块中的语句。
(8)优先级最高的是 finally 块;即使 try 或 catch 语句块中有 return 语句,也是先执行完 finally 块中的语句,再回到 try 或 catch 块中执行 return 语句 (注意,return 后的语句的变量,系统会用一个临时变量保存),但如果 finally 块中同样有 return 语句,则会直接退出方法,不再回到try 或 catch 块。
- 代码举例:
class ExceptionExe01 {
public static void main(String[] args) {
System.out.println(method());
// 输出:i = 4
// 3
}
public static int method() {
int i = 1;// i = 1
try {
i++;// i = 2
String[] names = new String[3];
if (names[1].equals("tom")) {
// 空指针异常
System.out.println(names[1]);
} else {
names[3] = "hspedu";
}
return 1;
} catch (ArrayIndexOutOfBoundsException e) {
return 2;
} catch (NullPointerException e) {
return ++i; // 执行++i,i = 3 => 保存临时变量 temp = 3;但不执行return
// 执行完 finally 块后再回来执行 return;
} finally {
++i; //i = 4
System.out.println("i=" + i);// i = 4
}
}
}
5.2 throws 异常处理
5.2.1 throws 基本用法
(1)如果一个方法中的语句执行时可能产生某种异常,但是并不能确定如何处理这种异常(try-catch),则该方法标签中应该声明抛出该异常,表明该方法将不直接对这些异常进行处理,而是交由该方法的调用者负责处理。
(2)在方法标签的声明中用 throws 关键字可以声明抛出异常的列表(可以同时抛出多种异常),throws 后面的异常类型可以是方法中产生的异常类型,也可以是它的父类。
- 代码举例:
public class Throws01 {
public static void main(String[] args) {
}
public void f2() throws FileNotFoundException,NullPointerException,ArithmeticException {
//创建了一个文件流对象
//1. 这里的异常是一个FileNotFoundException 编译异常
//2. 可以使用前面讲过的 try-catch-finally
//3. 或者使用throws ,抛出异常, 让调用f2方法的调用者(方法)处理
//4. throws后面的异常类型可以是方法中产生的异常类型,也可以是它的父类
//5. throws 关键字后也可以是 异常列表, 即可以抛出多个异常
FileInputStream fis = new FileInputStream("d://aa.txt");
}
}
5.2.2 throws 处理机制示意图
- 如下图:
5.2.3 throws 注意事项和细节
(1)对于编译时异常,程序中必须处理,可以使用 try-catch 或者 throws 处理;但是要注意:使用 throws 抛出时,一定要在产生异常的方法标签中显式地声明 throws 抛出(一定要写出来)。
(2)对于运行时异常,不一定要处理;若方法中产生运行时异常且没有处理时,默认在该方法标签中隐式地声明了 throws 抛出处理,最上层会抛给 JVM来处理。
- 代码举例:
public class ThrowsDetail {
public static void main(String[] args) {
f2();
}
public static void f2() /*隐含:throws ArithmeticException*/ {
//1.对于编译异常,程序中必须处理,比如 try-catch 或者 throws
//2.对于运行时异常,程序中如果没有处理,默认就是throws的方式处理
int n1 = 10;
int n2 = 0;
double res = n1 / n2;
}
public static void f1() throws FileNotFoundException {
//1. f3() 方法抛出的是一个编译异常
//2. 即这时,就要f1() 必须处理这个编译异常
//3. 在f1() 中,要么 try-catch-finally ,或者继续throws 这个编译异常
f3(); // 抛出异常
}
public static void f3() throws FileNotFoundException {
FileInputStream fis = new FileInputStream("d://aa.txt");
}
public static void f4() {
//1. 在f4()中调用方法f5() 是可以
//2. 原因是f5() 抛出的是运行时异常
//3. 而java中,并不要求程序员显式地处理,因为默认throws的方式处理机制
f5();
}
public static void f5() throws ArithmeticException {
}
}
(3)子类重写父类的方法时,若父类方法抛出了异常,则子类重写的方法也要抛出异常,且子类方法抛出的异常类型要么和父类方法抛出的一致,要么为父类抛出异常的子类型。
- 代码举例:
class Father {
//父类
public void method() throws RuntimeException {
}
}
class Son extends Father {
//子类
//3. 子类重写父类的方法时,对抛出异常的规定:子类重写的方法,
// 所抛出的异常类型要么和父类抛出的异常一致,要么为父类抛出的异常类型的子类型
@Override
public void method() throws ArithmeticException {
}
}
(4)在异常向上抛出的过程中,若有方法使用 try-catch 处理了该异常,就不用再向上继续抛出了。
6. 自定义异常
6.1 自定义异常的基本概念
-
当程序中出现了某些异常,但该异常信息并没有在 Throwable 子类中包含,这个时候可以自己设计异常类,用于描述该异常信息。(注意:自定义异常是异常类,并没有处理异常)
-
基本定义步骤:
(1)定义异常类:自定义异常类的类名,继承 Exception 类或者 RuntimeException 类。
(2)如果继承了 Exception 类,则属于编译时异常类。
(3)如果继承了 RuntimeException 类,则属于运行时异常类(一般都是继承RuntimeException 类)。
- throws 关键字:用于显式地抛出一个自定义异常对象(提示此处会存在一个自定义异常),该对象可以 被 try-catch 捕获并处理,或者被 throws 抛出。
6.2 自定义异常的应用案例
- 案例如下:
- 代码实现:
public class CustomException {
public static void main(String[] args) /*隐含:throws AgeException*/ {
int age = 180;
//要求范围在 18 – 120 之间,否则抛出一个自定义异常
if(!(age >= 18 && age <= 120)) {
//这里我们可以通过构造器,设置异常信息
throw new AgeException("年龄需要在 18~120之间");
// 此处抛出了一个自定义异常对象,使用默认的 throws 处理该异常;
}
System.out.println("你的年龄范围正确.");
}
}
//自定义一个异常类
//1. 一般情况下,我们自定义异常是继承 RuntimeException
//2. 即把自定义异常做成 运行时异常,好处是,我们可以使用默认的处理机制
class AgeException extends RuntimeException {
//构造器
public AgeException(String message) {
super(message);
}
}
6.3 throw 和 throws 的区别
- 如下图:
总结
- 本文是小白博主在学习B站韩顺平老师的Java网课时整理总结的学习笔记,在这里感谢韩顺平老师的网课,如有有兴趣的小伙伴也可以去看看。
- 本文详细解释了 异常 的概念与使用,并深入讲解了 异常 使用的注意事项和细节,还举了很多很多例子,希望小伙伴们看后能有所收获!
- 最后,如果本文有什么错漏的地方,欢迎大家批评指正!一起加油!!我们下一篇博文见吧!