10.Java 异常处理:捕获、抛出与自定义异常

        在 Java 编程世界里,异常处理是保障程序健壮性和可靠性的重要机制。它能够帮助开发者识别和处理程序运行过程中出现的各种错误情况,使程序在面对异常时能够优雅地应对,而不是突然崩溃。

一、Java 异常体系​

        Java 的异常体系是一个基于类的层次结构,其顶层是 Throwable 类,它有两个重要的直接子类:Exception(异常)和 Error(错误)。

1. Throwable​

        Throwable 是 Java 异常体系的根类,它包含了异常的基本信息,如错误消息、堆栈跟踪等。所有的异常和错误都是 Throwable 的子类。​

2. Error​

        Error 表示严重的系统错误,通常是由硬件故障、虚拟机错误等不可抗拒的因素引起的。这类错误发生时,程序往往无法正常处理,例如 OutOfMemoryError(内存溢出错误)、StackOverflowError(栈溢出错误)等。在实际开发中,我们通常不需要显式地处理 Error,因为程序无法恢复这类错误。​

3. Exception​

        Exception 表示程序运行过程中出现的异常情况,它又可以分为受检异常(Checked Exception)和非受检异常(Unchecked Exception)。​

(1)受检异常​

        受检异常是在编译时期就需要被处理的异常。如果方法可能抛出受检异常,那么调用该方法时必须显式地使用 try-catch 语句捕获异常,或者在方法声明中使用 throws 关键字抛出异常。常见的受检异常包括 IOException、SQLException 等。​

(2)非受检异常​

        非受检异常是在运行时期才会出现的异常,它们的基类是 RuntimeException。这类异常通常是由于程序逻辑错误引起的,例如 NullPointerException(空指针异常)、ArrayIndexOutOfBoundsException(数组下标越界异常)等。对于非受检异常,编译器不会强制要求处理,开发者可以根据实际情况选择是否处理。​

Java 异常体系的层次结构可以用下图来表示

Throwable
├── Error(错误,无需显式处理)
└── Exception(异常)
    ├── RuntimeException(非受检异常,无需强制处理)
    │   ├── NullPointerException
    │   ├── ArrayIndexOutOfBoundsException
    │   └── ...
    └── 其他异常(受检异常,必须处理)
        ├── IOException
        ├── SQLException
        └── ...

 

二、try-catch-finally 语句使用​

        try-catch-finally 是 Java 中处理异常的核心语句,它用于捕获和处理程序中可能抛出的异常。​

1. 基本语法

try {
    // 可能抛出异常的代码
} catch (ExceptionType1 e1) {
    // 处理 ExceptionType1 类型的异常
} catch (ExceptionType2 e2) {
    // 处理 ExceptionType2 类型的异常
} finally {
    // 无论是否发生异常,都会执行的代码
}

2. 各部分作用​

  • try 块:包含可能抛出异常的代码。在 try 块中,如果发生异常,程序会立即跳转到对应的 catch 块进行处理;如果没有发生异常,程序会继续执行 try 块后面的代码(如果有的话)。​
  • catch 块:用于捕获并处理特定类型的异常。一个 try 块可以有多个 catch 块,用于处理不同类型的异常。需要注意的是,catch 块的顺序应该是从子类到父类,即子类异常的 catch 块应该放在父类异常的 catch 块前面,否则会导致编译错误。​
  • finally 块:finally 块中的代码无论是否发生异常,都会执行。它通常用于释放资源,如关闭文件流、数据库连接等,确保资源的正确释放。​

3. 示例​

下面通过一个简单的示例来演示 try-catch-finally 语句的使用:

public class TryCatchFinallyDemo {
    public static void main(String[] args) {
        int a = 10;
        int b = 0;
        try {
            int result = a / b; // 可能抛出 ArithmeticException 异常
            System.out.println("结果:" + result);
        } catch (ArithmeticException e) {
            System.out.println("捕获到算术异常:" + e.getMessage());
        } finally {
            System.out.println("finally 块执行");
        }
        System.out.println("程序继续执行");
    }
}

        在上述示例中,由于除数为 0,会抛出 ArithmeticException 异常。程序会跳转到对应的 catch 块处理异常,然后执行 finally 块中的代码,最后继续执行 try-catch-finally 语句后面的代码。​

 

三、实际场景演示​

1. 文件读取场景​

        在文件读取过程中,可能会遇到文件不存在、读取错误等异常情况。下面以读取文本文件为例,演示如何处理这些异常:

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class FileReadDemo {
    public static void main(String[] args) {
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new FileReader("test.txt"));
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (FileNotFoundException e) {
            System.out.println("文件未找到:" + e.getMessage());
        } catch (IOException e) {
            System.out.println("文件读取错误:" + e.getMessage());
        } finally {
            try {
                if (reader != null) {
                    reader.close(); // 关闭文件流,释放资源
                }
            } catch (IOException e) {
                System.out.println("关闭文件流时发生错误:" + e.getMessage());
            }
        }
    }
}

        在上述代码中,首先尝试创建 BufferedReader 对象来读取文件。如果文件不存在,会抛出 FileNotFoundException 异常;如果在读取过程中发生其他 IO 错误,会抛出 IOException 异常。在 finally 块中,确保关闭文件流,即使在关闭过程中发生异常,也会进行处理。​

2. 网络请求场景​

        在网络请求中,可能会遇到连接超时、读取超时、网络中断等异常情况。下面以使用 Java 的 URL 和 URLConnection 类发送 HTTP 请求为例,演示异常处理:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;

public class NetworkRequestDemo {
    public static void main(String[] args) {
        String urlString = "http://www.example.com";
        BufferedReader reader = null;
        try {
            URL url = new URL(urlString);
            URLConnection connection = url.openConnection();
            connection.setConnectTimeout(5000); // 设置连接超时时间为 5 秒
            connection.setReadTimeout(5000); // 设置读取超时时间为 5 秒
            reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (MalformedURLException e) {
            System.out.println("URL 格式错误:" + e.getMessage());
        } catch (IOException e) {
            System.out.println("网络请求错误:" + e.getMessage());
        } finally {
            try {
                if (reader != null) {
                    reader.close();
                }
            } catch (IOException e) {
                System.out.println("关闭输入流时发生错误:" + e.getMessage());
            }
        }
    }
}

        在这个示例中,处理了 MalformedURLException(URL 格式错误异常)和 IOException(网络 IO 异常)。通过设置连接超时和读取超时时间,确保网络请求不会无限等待。在 finally 块中关闭输入流,释放资源。​

四、自定义业务异常​

        在实际开发中,我们经常需要根据业务需求定义自己的异常类型,以便更准确地描述业务逻辑中出现的错误情况。自定义业务异常通常继承自 Exception(受检异常)或 RuntimeException(非受检异常)。​

1. 为什么需要自定义业务异常​

  • 提高代码的可读性和可维护性:自定义异常能够更清晰地表达业务逻辑中的错误情况,使其他开发者更容易理解代码。​
  • 方便异常处理:可以根据自定义异常的类型,在不同的层次进行针对性的处理。

2. 实现步骤​

(1)定义自定义异常类
// 受检异常(继承自 Exception)
public class UserNameAlreadyExistsException extends Exception {
    public UserNameAlreadyExistsException(String message) {
        super(message);
    }
}

// 非受检异常(继承自 RuntimeException)
public class InvalidPasswordException extends RuntimeException {
    public InvalidPasswordException(String message) {
        super(message);
    }
}
(2)使用自定义异常​

下面以用户注册场景为例,演示如何使用自定义异常:

public class UserRegistration {
    public static void registerUser(String userName, String password) throws UserNameAlreadyExistsException {
        // 模拟检查用户名是否已存在
        if ("existingUser".equals(userName)) {
            throw new UserNameAlreadyExistsException("用户名已存在");
        }
        // 模拟检查密码是否合法
        if (password.length() < 6) {
            throw new InvalidPasswordException("密码长度不能小于 6 位");
        }
        System.out.println("用户注册成功");
    }

    public static void main(String[] args) {
        try {
            registerUser("existingUser", "123");
        } catch (UserNameAlreadyExistsException e) {
            System.out.println("注册失败:" + e.getMessage());
        } catch (InvalidPasswordException e) {
            System.out.println("注册失败:" + e.getMessage());
        }
    }
}

        在上述代码中,定义了两个自定义异常:UserNameAlreadyExistsException(受检异常,用于处理用户名已存在的情况)和 InvalidPasswordException(非受检异常,用于处理密码不合法的情况)。在 registerUser 方法中,根据业务逻辑抛出相应的异常。在 main 方法中,捕获并处理这些异常。

五、总结​

        本文详细介绍了 Java 异常体系,包括 Throwable、Error 和 Exception 的关系,以及受检异常和非受检异常的区别。通过 try-catch-finally 语句的使用方法和实际场景演示,展示了如何捕获和处理异常。最后介绍了自定义业务异常的实现方法,以满足实际开发中的业务需求。​

        在编写 Java 程序时,合理使用异常处理机制能够提高程序的健壮性和可读性。开发者应该根据不同的场景选择合适的异常处理方式,对于受检异常,必须显式地处理;对于非受检异常,要注意避免程序逻辑错误。同时,自定义业务异常能够使代码更加清晰地表达业务逻辑,方便后续的维护和扩展。希望本文能够帮助读者更好地理解和掌握 Java 异常处理的相关知识。

你可能感兴趣的:(#,Java,核心技术,java,开发语言,后端)