1.异常
1.1 异常概述
异常是指程序在运行过程中出现的非正常的情况,如用户输入错误、除数为零、文件不存在、数组下标越界等。由于异常情况再程序运行过程中是难以避免的,一个良好的应用程序除了满足基本功能要求外,还应具备预见并处理可能发生的各种异常情况。因此,在开发中需要充分考虑各种意外情况,以提高程序的容错能力。异常处理事一种技术,用于处理这种异常情况。
我们阅读下面的代码,通过这段代码来认识异常。 我们调用一个方法时,经常一不小心就出异常了,然后在控制台打印一些异常信息。其实打印的这些异常信息,就叫做异常。
public class ExceptionTest01 {
public static void main(String[] args) {
System.out.println(Integer.valueOf("abc"));
}
}
运行之后会出现如下的页面:
因为在设计这个方法的时候,在方法中对调用者传递的参数进行校验,如果校验数据不合法,就会将异常信息封装成一个对象并抛出,JVM接收到异常对象之后,就把异常打印了。
public static Integer valueOf(String s) throws NumberFormatException {
return Integer.valueOf(parseInt(s, 10));
}
因为写代码时经常会出现问题,Java的设计者们早就为我们写好了很多个异常类,来描述不同场景下的问题。而有些类是有共性的所以就有了异常的继承体系。
1.1.1 异常的体系
所有异常类都是Throwable类的子类,它派生出两个子类, Error
类和 Exception
类:
(1) Error
类:表示程序无法恢复的严重错误或者恢复起来比较麻烦的错误,例如内存溢出、动态链接失败、虚拟机错误等。应用程序不应该主动抛出这种类型的错误,通常由虚拟机自动抛出。如果出现这种错误,最好的处理方式是让程序安全退出。在进行程序设计时,我们更应关注Exception
类。
(2)Exception
类:由Java应用程序抛出和处理的非严重错误,例如文件未找到、网络连接问题、算术错误(如除以零)、数组越界、加载不存在的类、对空对象进行操作、类型转换异常等。Exception类的不同子类对应不同类型的异常。Exception
类又可分为两大类异常:
- 不受检异常:也称为unchecked异常,包括
RuntimeException
及其所有子类。对这类异常并不要求强制进行处理,例如算术异常ArithmeticException
等。 - 受检异常:也称为checked异常,指除了不受检异常外,其他继承自
Exception
类的异常。对这类异常要求在代码中进行显式处理。
Java提供了多种异常类,下表列举了一些常见的异常类及其用途:
1.2 Java异常处理机制
1.2.1 异常处理
Java的异常处理机制类似于人们对可能发生的意外情况进行预先处理的方式。在程序执行过程中,如果发生了异常,程序会按照预定的处理方式对异常进行处理。处理完异常之后,程序会继续执行。如果异常没有被处理,程序将会终止运行。
Java的异常处理机制依靠以下 5 个关键字:
try
、catch
、finally
、throw
、throws
。这些关键字提供了两种异常处理方式。
(1)使用try、catch、finally来捕获和处理异常:
- try 块中包含可能会抛出异常的代码。
- catch 块中用于捕获并处理指定类型的异常。
- finally 块中的代码无论是否发生异常都会被执行,通常用于释放资源或清理操作。
package swp.kaifamiao.codes.Java.d0830;
/**
* {class description}
*
* @author SWP
* @version 1.0.0
*/
public class Main {
public static void main(String[] args) {
try {
int i = 1, j = 0, res;
System.out.println("begin");
res = i / j;
System.out.println("end");
}catch (ArithmeticException e){
System.out.println("caught");
e.printStackTrace();
}finally {
System.out.println("finally");
}
System.out.println("over");
}
}
运行效果:
(2)使用throw、throws 来抛出异常:
① 使用throws声明抛出异常
try-catch-finally 处理的是在方法内部发生的异常,在方法内部直接捕获并处理。如果在一个方法体内抛出了异常,并希望调用者能够及时地捕获异常,Java语言中通过关键字throws声明某个方法可能抛出的各种异常,以通知调用者。throws 可以同时声明多个异常,之间用逗号隔开。
package swp.kaifamiao.codes.Java.d0830;
import java.util.InputMismatchException;
import java.util.Scanner;
/**
* {class description}
*
* @author SWP
* @version 1.0.0
*/
public class Demo01 {
public static void main(String[] args) {
try {
divide();
}catch (InputMismatchException e){
System.out.println("除数和被除数必须都是整数");
}catch (ArithmeticException e){
System.out.println("除数不能为零");
}catch (Exception e){
System.out.println("其他异常" + e.getMessage());
}finally {
System.out.println("感谢使用本程序");
}
System.out.println("程序结束");
}
/**
通过throws声明抛出设计时异常
*/
public static void divide() throws Exception{
Scanner input = new Scanner(System.in);
System.out.println("计算开始");
int i, j, res;
System.out.println("请输入被除数");
i = input.nextInt();
System.out.println("请输入除数");
j = input.nextInt();
res = i / j;
System.out.println(i + "/" + j + "=" + res);
System.out.println("计算结束");
}
}
②使用throw声明抛出异常
除了系统自动抛出异常外,在编程过程中,有些问题是系统无法自动发现并解决的,如年龄不在正常范围之内,性别输入的不是“男”或“女”等,此时需要程序员而不是系统来自行抛出异常,把问题提交给调用者去解决。在Java语言中,可以使用throw关键字来自行抛出异常。
throw new Exception("message")
- 如果 throw 语句抛出的异常是 Checked 异常,则该 throw 语句要么处于 try 块⾥,显式捕获该异常,要么放在⼀个带 throws 声明抛出的⽅法中,即把该异常交给该⽅法的调⽤者处理;
- 如果 throw 语句抛出的异常是 Runtime 异常,则该语句⽆须放在 try 块⾥,也⽆须放在带 throws 声明抛出的⽅法中;程序既可以显式使⽤ try…catch来捕获并处理该异常,也可以完全不理会该异常,把该异常交给该⽅法调⽤者处理。
自行抛出Runtime 异常比自行抛出Checked 异常的灵活性更好。同样,抛出 Checked 异常则可以让编译器提醒程序员必须处理该异常。
1.2.2 自定义异常
当JDK中的异常类型不能满足程序需求时,可以自定义异常类,使用自定义异常类一般有以下几个步骤:
(1)定义异常类,并继承Exception
或RuntimeException
;
(2)编写异常类的构造方法,向父类构造方法传入异常描述信息,并继承父类的其他实现方法;
(3)实例化自定义异常对象,并在程序中使用throw抛出。
举例:实现以下需求
需求:写一个saveAge(int age)方法,在方法中对参数age进行判断,如果age<0或者>=150就认为年龄不
合法,如果年龄不合法,就给调用者抛出一个年龄非法异常。
分析:Java的API中是没有年龄非常这个异常的,所以我们可以自定义一个异常类,用来表示年龄非法异常,然后再方法中抛出自定义异常即可。
自定义一个AgeIllegalException
异常类:
package swp.kaifamiao.codes.Java.d0830;
/**
* {class description}
* 自定义异常类 AgeIllegalException
* 继承 Exception 类
* 用于表示年龄不合法的异常
* @author SWP
* @version 1.0.0
*/
public class AgeIllegalException extends Exception {
public AgeIllegalException() {
}
public AgeIllegalException(String message) {
super(message);
}
}
package swp.kaifamiao.codes.Java.d0830;
/**
* {class description}
* 异常测试类 ExceptionTest
* 用于测试自定义异常 AgeIllegalException
* 当年龄不合法时抛出 AgeIllegalException 异常
* 并在 main 方法中捕获并处理该异常
* @author SWP
* @version 1.0.0
*/
public class ExceptionTest {
public static void main(String[] args) {
try {
saveAge(225);
System.out.println("saveAge2底层执行是成功的!");
} catch (AgeIllegalException e) {
e.printStackTrace();
System.out.println("saveAge2底层执行是出现bug的!");
}
}
// 在方法中对age进行判断,不合法则抛出AgeIllegalException
public static void saveAge(int age) throws AgeIllegalException {
if (age > 0 && age < 150) {
System.out.println("年龄被成功保存: " + age);
} else {
// 用一个异常对象封装这个问题
// throw 抛出去这个异常对象
throw new AgeIllegalException("age is illegal, your age is " + age);
}
}
}
运行效果:
1.2.3 异常链
异常链(Exception Chaining)是指在异常处理过程中,将当前的异常作为原因(cause)链接到另一个异常上。通过异常链,可以追踪异常发生的完整路径,并提供更多的上下文信息。
(1)定义testOne,testTwo,testThree方法,testTwo对testOne抛出的异常进行捕获,testThree对testTwo抛出的异常进行捕获:
package swp.kaifamiao.codes.Java.d0830;
/**
* {class description}
*
* @author SWP
* @version 1.0.0
*/
public class TryDemoFive {
public static void main(String[] args) {
try {
testThree();
} catch (Exception e) {
// 打印完整的异常信息
e.printStackTrace();
}
}
public static void testOne() throws MyException {
throw new MyException("我是一个异常");
}
public static void testTwo() throws Exception {
try {
testOne();
} catch (Exception e) {
// 在新抛出的异常中添加原来的异常信息
throw new Exception("我是新产生的异常1", e);
}
}
public static void testThree() throws Exception {
try {
testTwo();
} catch (Exception e) {
// 在要抛出的对象中使用 initCause() 方法,添加上一个产生异常的信息
Exception e2 = new Exception("我是新产生的异常2");
e2.initCause(e);
throw e2;
}
}
}
(2)定义MyException 类:
package swp.kaifamiao.codes.Java.d0830;
/**
* {class description}
*
* @author SWP
* @version 1.0.0
*/
public class MyException extends Exception{
public MyException(String message) {
super(message);
}
}
(3)运行效果:
2.思考
2.1 什么是异常?
异常是指程序在运行过程中出现的非正常的情况,如用户输入错误、除数为零、文件不存在、数组下标越界等。
2.2 什么是运行时异常?
运行时异常(Runtime Exception)是指在程序运行期间可能抛出的异常,它们属于非受检异常(Unchecked Exception)。与受检异常(Checked Exception)相比,运行时异常在编译期不需要强制处理或声明。
运行时异常通常表示程序的逻辑错误或者错误的使用方式,例如数组越界、空指针引用等。这些异常通常是由程序员编码时的错误导致的,但在编译时却无法确定是否会发生异常。
2.3 如何处理异常?
(1) 使用try、catch、finally来捕获和处理异常;
(2) 使用throw、throws 来抛出异常
2.4 什么是checked异常?什么是unchecked异常?
(1) 受检异常是在编译时强制要求处理的异常,需要使用 try-catch 块捕获并处理,或者在方法签名中使用 throws 关键字声明异常。
(2) 非受检异常是由程序逻辑错误或错误的使用方式引起的异常,在编译时不需要强制要求处理,但仍可以选择性地进行捕获和处理。
2.5 构造方法可以throws异常吗?对子类有影响吗?有什么影响?
可以在构造方法中抛出异常,与其他方法一样,构造方法也可以声明受检异常并通过 throws 关键字将其抛出。
如果在父类的构造方法中声明了受检异常,那么所有继承自该类的子类构造方法必须显示地处理这些异常或者在它们的方法签名中使用 throws 关键字将其抛出。否则编译会报错。
2.6 throw和throws的区别?
(1) 作⽤不同:throw⽤于程序员⾃⾏产⽣并抛出异常,throws⽤于声明该⽅法内抛出了异常。
(2) 使⽤位置不同:throw位于⽅法体内部,可以作为单独的语句使⽤;throws必须跟在⽅法参数列表的后⾯,不能单独使⽤。
(3) 内容不同:throw抛出⼀个异常对象,只能是⼀个;throws后⾯跟异常类,可以跟多个。
2.7 能否自己throw一个Error?
可以使用 throw 关键字抛出一个 Error 对象。在Java中,Error 是 Throwable 类的子类,它表示严重的错误和异常情况,通常由Java虚拟机(JVM)或底层系统引起,例如 OutOfMemoryError、StackOverflowError 等。
与 Exception 不同,Error 通常表示不可恢复的错误或系统级故障,它们是无法预料和处理的,一般不建议程序员捕获和处理 Error。通常情况下,Error 会导致程序中止执行。
以下是一个示例,演示如何抛出一个 Error:
public class Example {
public static void main(String[] args) {
throw new Error("This is an error.");
}
}
需要注意的是,抛出 Error 可能会导致程序异常终止,并且不应该被常规的异常处理机制所捕获和处理。因此,在编写代码时,通常不建议自己抛出 Error,除非确实具有特殊的需求或深入了解它们的影响。一般的异常处理应该针对 Exception 及其子类。
2.8 假如throw里面有return语句,catch里面有return语句,finally里面也有return语句,为什么最后返回的是finally里面的return语句?
在 Java 中,finally 块中的 return 语句会覆盖之前的 try 或 catch 块中的 return 语句,并决定最终的返回值。这是因为无论在 try、catch 或者 finally 块中执行了哪个 return 语句,都会直接结束整个方法并返回对应的值。
根据 Java 语言规范,finally 块中的 return 语句会在方法返回之前执行,以确保在方法返回之前可以进行一些必要的清理工作。所以,在 finally 块中使用 return 语句将决定最终的返回值。
2.9 为啥要自定义异常?如何自定义异常?
当JDK中的异常类型不能满足程序需求时,可以自定义异常类,自定义异常在编程中非常有用,它能够提供更加准确和具体的异常信息,并能够满足特定业务需求。通过自定义异常,可以将代码中可能发生的异常情况进行分类和处理,使代码更加清晰、可读性更高,并且有助于调试和错误处理。
(1)定义异常类,并继承`Exception` 或`RuntimeException`;
(2)编写异常类的构造方法,向父类构造方法传入异常描述信息,并继承父类的其他实现方法;
(3)实例化自定义异常对象,并在程序中使用throw抛出。