异常(Exception)是程序在运行过程中发生的事件,通常导致程序的正常流程中断。Java通过异常处理机制,允许开发者捕获并处理这些异常,确保程序能够在遇到错误时表现得更加优雅和可靠。
异常分类
在Java中 Throwable 是 Java 中所有错误和异常的超类,下层是 Exception 和 Error
Exception
Exception 是程序正常运行中,可以预料的意外情况,比如数据库连接中断,空指针,数组下标越界。异常出现可以导致程序非正常终止,也可以预先检测被捕获处理掉,使程序继续运行,Exception 有两类
CheckedException
CheckedException是部分方法在声明时候使用 throws 关键字声明可能出现的异常,编译器会强制要求程序员对此类异常进行捕获或声明抛出,需要使用 try/catch、try-with-resources 等处理异常或者继续抛出异常,否则编译器就会报错。常见的已检查异常包括 IOException、SQLException 等
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
try {
File file = new File("input.txt");
Scanner scanner = new Scanner(file);
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
System.out.println(line);
}
scanner.close();
} catch (FileNotFoundException e) {
System.out.println("File not found: " + e.getMessage());
}
}
}
Scanner 主动声明编译异常
public Scanner(File source) throws FileNotFoundException {
this((ReadableByteChannel)(new FileInputStream(source).getChannel()));
}
如果使用 Scanner 没有用 try/catch 包裹,编译器会报错
RuntimeException
RuntimeException也被称为 UncheckedException,通常由程序逻辑错误引起,比如空指针、下标越界等,运行时异常应该在程序测试期间被暴露出来,由程序员去解决,而避免使用捕获的方式(实际开发时很难做到这点,还是会用 try/catch 保障程序稳定性)
public class UncheckedExceptionExample {
public static void main(String[] args) {
String str = null;
System.out.println(str.length()); // 会抛出 NullPointerException
}
}
Error
Error 表示更严重的错误,一般是 Java 运行时系统内部错误或资源耗尽错误,常见的有内存溢出、JVM 虚拟机非正常运行等,Error 会导致程序无法运行,应用程序不会主动抛出 Error,也无法处理 Error,出现后除了告知用户就是尽力使程序安全终止
异常处理机制
Java 提供了一套完整的异常处理机制,通过使用 try-catch-finally 语句块和 throw/throws 关键字,开发者可以有效地捕获和处理异常
try...catch...finally
finally 用于执行一定要执行的代码,无论是否发生异常。常用于释放资源,如关闭文件、数据库连接等。finally 可以省略
public class FinallyExample {
public static void main(String[] args) {
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("file.txt"));
String line = reader.readLine();
System.out.println(line);
} catch (IOException e) {
System.out.println("文件读取出错: " + e.getMessage());
} finally {
try {
if (reader != null) reader.close();
} catch (IOException e) {
System.out.println("关闭文件出错: " + e.getMessage());
}
}
}
}
try with resources
try-with-resources 是 Java 7 引入的一个语法糖,用于自动关闭实现了 AutoCloseable 接口的资源,如文件、数据库连接等,用以代替传统的 try-catch-finally 方法来关闭资源,使代码更简洁、可读性更高
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class Main {
public static void main(String[] args) {
// 括号中声明并初始化需要关闭的资源
try (BufferedReader br = new BufferedReader(new FileReader("input.txt"))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
System.out.println("文件读取或关闭出错: " + e.getMessage());
}
}
}
当 try 块结束时 Java 会自动调用 BufferedReader 对象的 close() 方法来关闭它。如果读取文件时发生异常,catch 块中的代码会处理异常,并且 BufferedReader 对象也会被自动关闭
throw 与 throws
throw 用于在方法内部主动抛出异常,throws 在方法签名中声明该方法可能抛出的异常,提醒调用者需要处理这些异常
public class ThrowsExample {
public static void main(String[] args) {
try {
processFile("file.txt");
} catch (IOException e) {
System.out.println("处理文件出错: " + e.getMessage());
}
}
public static void processFile(String fileName) throws IOException {
BufferedReader reader = new BufferedReader(new FileReader(fileName));
String line = reader.readLine();
System.out.println(line);
reader.close();
}
}
自定义异常
除了Java提供的内置异常,开发者还可以根据业务需求创建自定义异常类,以更准确地表达错误状态和处理逻辑。
- 继承异常类:自定义异常类需要继承自
Exception类(受检查异常)或RuntimeException类(运行时异常)。通常如果希望调用者必须处理该异常则继承Exception类;如果希望异常可以在运行时被抛出而不需要显式处理则继承RuntimeException类 - 添加构造方法:为自定义异常类添加构造方法,以便在抛出异常时可以传递错误信息
// 创建一个已检查异常
public class InvalidUserInputException extends Exception {
public InvalidUserInputException(String message) {
super(message);
}
}
// 创建一个未检查异常
public class DataProcessingException extends RuntimeException {
public DataProcessingException(String message) {
super(message);
}
}
使用自定义异常
public class CustomExceptionExample {
public static void main(String[] args) {
try {
validateAge(15);
} catch (InvalidUserInputException e) {
System.out.println("验证失败: " + e.getMessage());
}
}
public static void validateAge(int age) throws InvalidUserInputException {
if (age < 18) {
throw new InvalidUserInputException("年龄不能小于18岁!");
}
}
}
NPE 克星——Optional
使用 null 对象的方法或属性时候就会产生空指针异常,也就是大名鼎鼎的 NPE NullPointerException
Optional 类是在 Java 8 中引入的,作为java.util包的一部分,旨在用更优雅地处理可能为空的值,从而减少代码中的空指针异常
Optional<String> optional = Optional.ofNullable(null);
String value = optional.orElseGet(() -> "计算得到的默认值"); // "计算得到的默认值"
创建 Optional 对象
// 如果传入的值为null,则会抛出 NullPointerException
Optional<String> nonEmptyOptional = Optional.of("Hello");
// 如果传入的值为null,则返回一个空的Optional
Optional<String> nullableOptional = Optional.ofNullable(null);
// 创建一个空的Optional,不包含任何值
Optional<String> emptyOptional = Optional.empty();
检查值是否存在
如果 Optional 中包含一个值,isPresent()返回 true,否则返回 false
Optional<String> optional = Optional.of("Java");
if (optional.isPresent()) {
System.out.println("值存在");
}
在 Java 11 后可以使用 isEmpty(),相当于 isPresent()的反义
Optional<String> optional = Optional.empty();
if (optional.isEmpty()) {
System.out.println("Optional is empty");
}
获取值
使用 get()可以获取 Optional 中的值,如果没有值则抛出 NoSuchElementException
Optional<String> optional = Optional.of("World");
String value = optional.get(); // "World"
直接使用get()可能导致异常,一般会使用orElse()设置默认值
Optional<String> optional = Optional.ofNullable(null);
String value = optional.orElse("默认值");
或者使用orElseGet()通过 Supplier 函数计算默认结果
Optional<String> optional = Optional.ofNullable(null);
String value = optional.orElseGet(() -> "计算得到的默认值");
操作 Optional 对象
map:如果 Optional 中有值,应用提供的映射函数并返回包含结果的新的 Optional,否则返回空的 Optional
Optional<String> optional = Optional.of("Java");
Optional<Integer> lengthOptional = optional.map(String::length); // Optional[4]
flatMap:与 map 类似,但映射函数返回一个 Optional,会直接展开该 Optional,避免形成嵌套
import java.util.Optional;
public class OptionalFlatMapExample {
public static void main(String[] args) {
Optional<String> optionalEmail = findNameById(1)
.flatMap(name -> findEmailByName(name));
// 用户邮箱: alice@example.com
optionalEmail.ifPresent(email -> System.out.println("用户邮箱: " + email));
}
public static Optional<String> findNameById(int id) {
if (id == 1) {
return Optional.of("Alice");
} else {
return Optional.empty();
}
}
public static Optional<String> findEmailByName(String name) {
if ("Alice".equals(name)) {
return Optional.of("alice@example.com");
} else {
return Optional.empty();
}
}
}
如果函数使用的是 map 而不是 flatMap
Optional<Optional<String>> nestedOptionalEmail = findNameById(1)
.map(name -> findEmailByName(name));
// 输出: Optional[Optional[alice@example.com]]
System.out.println(nestedOptionalEmail);
这样会得到一个嵌套的 Optional<Optional<String>>,这在实际使用中并不方便。而 flatMap 可以避免这种情况,直接返回 Optional<String>
filter:如果 Optional 中有值,并且该值满足给定的谓词条件,则返回 Optional 本身;否则返回空的 Optional
Optional<String> optional = Optional.of("Java");
Optional<String> filtered = optional.filter(s -> s.startsWith("J")); // Optional["Java"]
Optional<String> emptyFiltered = optional.filter(s -> s.startsWith("X")); // Optional.empty()
isPresent:如果 Optional 中有值,执行提供的消费者操作
Optional<String> optional = Optional.of("Java");
optional.ifPresent(System.out::println); // 输出: Java
最佳实践
- 不要使用异常捕获进行流程控制
- 不要捕获 Throwable 或 Error
- 捕获具体的子类而不是捕获 Exception 类,抛出具体定义的检查性异常而不是 Exception
- 不要在 fianlly 块中使用 return。try 块中的 return 语句执行成功后并不立马返回,而是继续执行 finally 里面的语句,如果 finally 中包含 return 语句,会导致 try 中的 return 失效
- 早 throw 晚 catch。在代码中尽可能早地抛出异常,以便在异常发生时能够及时地处理异常。同时在 catch 块中尽可能晚地捕获异常,以便在捕获异常时能够获得更多的上下文信息
- 自定义异常不要丢失堆栈追踪,使用日志框架(如 Log4j、SLF4J)记录异常信息,而不是仅在控制台输出,有助于后续的监控和分析
catch (ClassNotFoundException e) {
throw new MyException("Something wrong: " + e.getMessage()); // 错误方式
}
catch (ClassNotFoundException e) {
throw new MyException("Something wrong: " + e); // 正确方式
}