一、前言
记录时间 [2024-05-22]
前置文章:
Java 异常相关:异常体系结构,异常与错误的区别等
本文讲述 Java 异常相关知识,对异常处理机制进行补充,以及自定义异常的方式。
文章对异常知识的描述显然是笼统而简洁的,对于异常的学习,更应注重平时的积累和整理。
二、异常处理机制
Java 通过 try-catch-finally
语句块来捕获和处理异常。5 个处理机制关键字如下:
- try - 用于包围可能会抛出异常的代码块。
- catch - 紧跟在
try
块之后,用于捕获并处理特定类型的异常。 - finally - 无论是否发生异常,都会执行的代码块,通常用于释放资源等善后工作。
- throw - 用于在代码中手动抛出一个异常实例。
- throws - 用于声明方法可能抛出的异常类型,将异常处理的责任转移给方法的调用者。
1. try-catch-finally
语法
// try 和 catch 是必须的,finally 可以不要
// 可以在 finally 中关闭一些 IO、资源
try {
// try 监控区域
} catch (ArithmeticException e) {
// catch 捕获异常
} finally {
// 处理善后工作
}
简单示例
以下是一个使用 try-catch-finally
语句处理异常的简单示例。
public static void main(String[] args) {
int a = 1;
int b = 0;
// try 和 catch 是必须的,finally 可以不要
// 可以在 finally 中关闭一些 IO、资源
try { // try 监控区域
System.out.println(a/b);
} catch (ArithmeticException e) { // catch 捕获异常
System.out.println("程序出现异常,变量 b 不能为 0");
} finally { // 处理善后工作
System.out.println("finally");
}
}
使用 try-catch-finally
语句捕获一个被除数为 0 的异常:
- 把需要监控的代码放在
try
区域; - 经由
catch
捕获异常后,我们可以做一些处理,比如输出问题; - 其中,
catch
中的参数是想要捕获的异常类型,如ArithmeticException
; - 最后,在
finally
中完成善后工作,无论异常是否被捕获,finally
最终都会执行。
错误 Error
也可以被捕获。
如果想要捕获多种异常,可以写多个 catch
语句块。
例如:
// 快捷键 Ctrl + Alt + T,快速生成代码块
try { // try 监控区域
System.out.println(a/b);
} catch (ArithmeticException e) { // catch 捕获异常
System.out.println("程序出现异常,变量 b 不能为 0");
} catch (Exception e) {
System.out.println("Exception");
} catch (Throwable t) {
System.out.println("Throwable");
} finally { // 处理善后工作
System.out.println("finally");
}
需要注意的是,catch
语句块自上而下层层递进,范围大的异常或错误捕获需要写在下面。
2. throw
在 Java 中,throw
关键字用于手动抛出一个异常。当需要在代码中表示某种异常情况发生时,就可以使用 throw
来创建并抛出一个异常对象。
这通常用于自定义异常处理逻辑,或者在方法内部检测到不符合条件的情况时主动抛出异常。
通过这种方式,可以根据程序的逻辑主动控制异常的抛出,使得异常处理更加灵活和精确。
语法
throw new ExceptionType("异常信息");
其中,ExceptionType
是用户希望抛出的异常类型,可以是具体的异常类名,如 IllegalArgumentException
、NullPointerException
等,也可以是自定义的异常类。
紧跟在后面的字符串是可选的,用于提供异常的具体描述信息。
简单示例
假设我们有一个方法,该方法接收一个年龄参数,如果年龄小于 0,我们希望抛出一个 IllegalArgumentException
来表示非法参数。
public class ThrowExample {
public static void main(String[] args) {
try {
checkAge(-1); // 这将抛出异常
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage()); // 打印异常信息
}
}
// 检查年龄的方法
public static void checkAge(int age) throws IllegalArgumentException {
if (age < 0) {
throw new IllegalArgumentException("年龄不能为负数");
} else {
System.out.println("年龄合法");
}
}
}
在这个例子中,checkAge
方法检查传入的年龄是否为负数。如果是,就通过 throw new IllegalArgumentException("年龄不能为负数")
抛出一个带有详细信息的 IllegalArgumentException
。
在 main
方法中,我们通过 try-catch
块捕获这个异常,并打印出异常信息。
3. throws
在 Java 中,throws
关键字用于声明方法可能抛出的异常类型,它告诉调用者这个方法执行时可能会遇到的问题,从而提醒调用者做出相应的异常处理。
使用 throws
关键字可以将异常处理的责任上交给方法的调用者。
通过 throws
声明异常,可以使代码更加清晰地表明哪些异常需要调用者关注并处理,有助于提高程序的可读性和健壮性。
语法
在方法签名后使用 throws
关键字,后面跟着可能抛出的一个或多个异常类型,用逗号分隔。
public returnType methodName() throws ExceptionType1, ExceptionType2 {
// 方法体
}
简单示例
考虑一个读取文件并返回其内容的函数,这个操作可能会抛出 IOException
。
我们可以使用 throws
来声明这个方法可能抛出的异常:
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class ThrowsExample {
public static void main(String[] args) {
try {
String content = readFileContent("example.txt");
System.out.println(content);
} catch (IOException e) {
System.out.println("读取文件时发生错误:" + e.getMessage());
}
}
// 声明可能抛出 IOException 的方法
public static String readFileContent(String filePath) throws IOException {
BufferedReader reader = new BufferedReader(new FileReader(filePath));
StringBuilder content = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
content.append(line).append("\n");
}
reader.close();
return content.toString();
}
}
在这个例子中,readFileContent
方法可能抛出 IOException
,因为它涉及到文件读取操作。
因此,我们在方法签名中使用 throws IOException
来声明这一可能性。
在 main
方法中调用 readFileContent
时,我们通过 try-catch
块来捕获并处理这个可能发生的异常。
三、自定义异常
使用 Java 内置的异常类可以描述在编程时出现的大部分异常情况。但是,在 Java 中,可能需要根据特定的业务逻辑或应用需求定义自己的异常类,这就是自定义异常。
自定义异常通常继承自 Exception
类或其子类(对于运行时异常,则继承自 RuntimeException
)。
自定义异常增强了程序的清晰度和可维护性,使得异常处理更贴近应用程序的具体逻辑。
下面是如何创建和使用自定义异常的简单步骤和示例。
1. 步骤
在程序中使用自定义异常类,大体可分为以下几个步骤:
- 定义异常类 - 新建一个类,继承自
Exception
或其子类(如IOException
、RuntimeException
等),根据异常的性质选择合适的基类。 - 构造方法 - 至少定义一个构造方法,通常接受一个字符串参数(异常消息),并调用超类的构造方法传递这个消息。
- 捕获并处理异常
- 在需要抛出自定义异常的地方,使用
throw new YourException("异常信息")
来抛出异常; - 如果在当前抛出异常的方法中处理异常,可以使用
try-catch
语句捕获并处理; - 否则在方法的声明处通过
throws
关键字指明要抛出给方法调用者的异常。
- 在需要抛出自定义异常的地方,使用
2. 示例
假设我们要为一个学生管理系统定义一个特定的异常,当尝试注册一个已经存在的学生 ID
时抛出。
我们可以创建一个 StudentAlreadyExistsException
,让这个类继承 Exception
为父类,它就变成了一个自定义异常类,处理特定的异常。
// 自定义异常类
public class StudentAlreadyExistsException extends Exception {
public StudentAlreadyExistsException(String message) {
// 调用Exception的构造方法传递异常信息
super(message);
}
}
然后,我们在其他方法中,使用这个自定义异常类处理一些特定的异常。
// 使用自定义异常的类
public class StudentManager {
private List<Student> students = new ArrayList<>();
public void registerStudent(Student student) throws StudentAlreadyExistsException {
for (Student s : students) {
if (s.getId().equals(student.getId())) {
// 主动抛出异常给上述自定义异常类
throw new StudentAlreadyExistsException("学生 ID 已存在,无法注册。");
}
}
students.add(student);
System.out.println("学生注册成功。");
}
public static void main(String[] args) {
StudentManager manager = new StudentManager();
Student student = new Student("S001", "张三");
try {
manager.registerStudent(student);
manager.registerStudent(student); // 尝试再次注册相同的 ID
} catch (StudentAlreadyExistsException e) {
System.out.println(e.getMessage());
}
}
}
在这个示例中:
- 我们首先定义了一个
StudentAlreadyExistsException
类,它继承自Exception
。 - 然后,在
StudentManager
类的registerStudent
方法中,当我们检测到尝试注册的学生ID
已存在时,就抛出这个自定义异常。 - 在
main
方法中,我们通过try-catch
块捕获并处理这个异常。
四、总结
本文讲述 Java 异常相关知识,对异常处理机制进行补充,以及自定义异常的方式。
- 处理运行时异常时,在逻辑上合理规避,同时使用
try-catch
辅助处理; - 在多重
catch
块后面,可以加一个catch (Exception e)
来处理可能会被遗漏的异常; - 对于不确定的代码,也可以加上
try-catch
,处理潜在的异常; - 尽量去处理异常,切忌只是简单地调用
printStackTrace()
去打印输出; - 具体如何处理异常,要根据不同的业务需求和异常类型去决定;
- 尽量添加
finally
语句块去释放占用的资源。
一些参考资料
狂神说 Java 零基础:https://www.bilibili.com/video/BV12J41137hu/
TIOBE 编程语言走势: https://www.tiobe.com/tiobe-index/
Typora 官网:https://www.typoraio.cn/
Oracle 官网:https://www.oracle.com/
Notepad++ 下载地址:https://notepad-plus.en.softonic.com/
IDEA 官网:https://www.jetbrains.com.cn/idea/
Java 开发手册:https://developer.aliyun.com/ebook/394
Java 8 帮助文档:https://docs.oracle.com/javase/8/docs/api/