【Java基础:系统性学习指南】异常处理:从语法到架构思维

摘要:本文围绕 Java 异常处理展开,从基础的 checked 与 unchecked 异常的区别入手,深入剖析 JDBC 和 Spring 选择不同异常类型的原因,通过具体案例探讨异常丢失的问题,从字节码层面解析 finally 块中 return 覆盖 try 块返回值的现象,最后详细介绍全局异常处理器的设计以及 Spring 的 @ControllerAdvice 实现原理。通过丰富的实操流程和完整代码,帮助开发者全面掌握 Java 异常处理的相关知识和架构思维。


文章目录


在这里插入图片描述

【Java基础:系统性学习】异常处理:从语法到架构思维

关键词

Java 异常处理;checked 异常;unchecked 异常;异常丢失;全局异常处理器;@ControllerAdvice

一、引言

在 Java 编程中,异常处理是一个至关重要的部分。它能够帮助开发者捕获和处理程序运行过程中出现的错误,保证程序的健壮性和稳定性。Java 中的异常分为 checked 异常和 unchecked 异常,不同的场景和框架会选择不同类型的异常进行处理。同时,异常处理过程中还可能会出现异常丢失等问题,需要开发者深入理解其原理并掌握相应的解决方法。此外,设计合理的全局异常处理器能够统一处理程序中的异常,提高代码的可维护性。本文将围绕这些核心内容,结合实际案例和代码,进行详细的阐述。

二、checked vs unchecked 异常

2.1 异常的基本概念

在 Java 中,异常是指程序在运行过程中出现的错误或意外情况。异常类继承自 Throwable 类,Throwable 有两个重要的子类:ErrorExceptionError 表示系统级的错误,通常是由 JVM 或硬件问题引起的,程序无法处理;Exception 表示程序可以处理的异常,又分为 checked 异常和 unchecked 异常。

2.2 checked 异常

checked 异常是指在编译时必须进行处理的异常,否则程序无法通过编译。这些异常通常表示程序外部的问题,如文件不存在、网络连接失败等。常见的 checked 异常包括 IOExceptionSQLException 等。

以下是一个简单的读取文件的示例,演示了 checked 异常的处理:

import java.io.File;
import java.io.FileReader;
import java.io.IOException;

public class CheckedExceptionExample {
   
    public static void main(String[] args) {
   
        try {
   
            File file = new File("test.txt");
            FileReader reader = new FileReader(file);
            int data;
            while ((data = reader.read()) != -1) {
   
                System.out.print((char) data);
            }
            reader.close();
        } catch (IOException e) {
   
            System.out.println("读取文件时出现异常: " + e.getMessage());
        }
    }
}

在这个示例中,FileReader 的构造函数和 read 方法都可能抛出 IOException,这是一个 checked 异常。因此,在使用这些方法时,必须使用 try-catch 块捕获异常或者在方法签名中使用 throws 关键字声明抛出异常。

2.3 unchecked 异常

unchecked 异常是指在编译时不需要进行处理的异常,也称为运行时异常。这些异常通常是由程序的逻辑错误引起的,如空指针异常、数组越界异常等。RuntimeException 及其子类都属于 unchecked 异常,常见的 unchecked 异常包括 NullPointerExceptionArrayIndexOutOfBoundsException 等。

以下是一个空指针异常的示例:

public class UncheckedExceptionExample {
   
    public static void main(String[] args) {
   
        String str = null;
        try {
   
            System.out.println(str.length());
        } catch (NullPointerException e) {
   
            System.out.println("出现空指针异常: " + e.getMessage());
        }
    }
}

在这个示例中,strnull,调用 length() 方法会抛出 NullPointerException,这是一个 unchecked 异常。虽然我们可以使用 try-catch 块捕获它,但在编译时并不强制要求。

2.4 checked 异常和 unchecked 异常的区别

  • 编译时检查:checked 异常在编译时会被检查,必须进行处理;而 unchecked 异常在编译时不会被检查。
  • 异常类型:checked 异常通常表示程序外部的问题,如文件操作、网络连接等;而 unchecked 异常通常表示程序的逻辑错误。
  • 处理方式:对于 checked 异常,必须使用 try-catch 块捕获或者在方法签名中使用 throws 关键字声明抛出;对于 unchecked 异常,可以选择捕获处理,也可以不处理。

三、为什么 JDBC 用 checked 异常而 Spring 用 unchecked

3.1 JDBC 使用 checked 异常的原因

JDBC(Java Database Connectivity)是 Java 用于连接数据库的标准 API。JDBC 使用 checked 异常(如 SQLException)的主要原因如下:

  • 明确错误处理:数据库操作涉及到外部资源,可能会出现各种错误,如数据库连接失败、SQL 语句执行错误等。使用 checked 异常可以强制开发者在代码中处理这些异常,确保程序能够正确地处理数据库操作中的错误。
  • 异常传播:在企业级应用中,数据库操作通常是多个层次的,如 DAO 层、Service 层等。使用 checked 异常可以将异常向上传播,让上层调用者知道数据库操作出现了问题,并进行相应的处理。

以下是一个简单的 JDBC 查询示例:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

public class JdbcExample {
   
    public static void main(String[] args) {
   
        Connection connection = null;
        Statement statement = null;
        ResultSet resultSet = null;
        try {
   
            // 加载数据库驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            // 建立数据库连接
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "password");
            // 创建 Statement 对象
            statement = connection.createStatement();
            // 执行 SQL 查询
            resultSet = statement.executeQuery("SELECT * FROM users");
            // 处理查询结果
            while (resultSet.next()) {
   
                System.out.println(resultSet.getString("username"));
            }
        } catch (Exception e) {
   
            System.out.println("数据库操作出现异常: " + e.getMessage());
        } finally {
   
            // 关闭资源
            try {
   
                if (resultSet != null) resultSet.close();
                if (statement != null) statement.close();
                if (connection != null) connection.close();
            } catch (Exception e) {
   
                System.out.println("关闭资源时出现异常: " + e.getMessage());
            }
        }
    }
}

在这个示例中,Class.forNameDriverManager.getConnectionstatement.executeQuery 等方法都可能抛出 SQLException 或其他 checked 异常,需要使用 try-catch 块进行处理。

3.2 Spring 使用 unchecked 异常的原因

Spring 是一个轻量级的 Java 开发框架,它提倡使用 unchecked 异常(如 DataAccessException 及其子类),主要原因如下:

  • 简化代码:使用 unchecked 异常可以避免在代码中大量使用 try-catch 块或 throws 关键字,使代码更加简洁。开发者可以将更多的精力放在业务逻辑上,而不是异常处理上。
  • 灵活性:Spring 框架中的异常处理机制更加灵活,它提供了全局异常处理器和异常转换器等功能,可以统一处理异常。使用 unchecked 异常可以更好地与这些机制结合,实现统一的异常处理。
  • 与业务逻辑分离:将异常处理与业务逻辑分离,使代码的结构更加清晰。业务逻辑只需要关注业务本身,异常处理由 Spring 框架统一负责。

以下是一个简单的 Spring 数据访问示例:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class UserService {
   
    @Autowired
    private JdbcTemplate jdbcTemplate;

    public List<String> getUsernames() {
   
        return jdbcTemplate.queryForList("SELECT username FROM users", String.class);
    }
}

在这个示例中,JdbcTemplate 的方法可能会抛出 DataAccessException 及其子类的异常,这些异常是 unchecked 异常,不需要在方法签名中声明抛出,也不需要在方法内部进行捕获处理。Spring 框架会在全局异常处理器中统一处理这些异常。

四、异常丢失的经典案例

4.1 异常丢失的概念

异常丢失是指在异常处理过程中,一个异常被另一个异常所覆盖,导致原始异常信息丢失的现象。这种情况通常会使调试和排查问题变得困难。

4.2 经典案例分析

4.2.1 在 finally 块中抛出异常
public class ExceptionLossInFinally {
   
    public static void main(String[] args) {
   
        try {
   
            // 模拟抛出异常
            throw new RuntimeException("原始异常");
        } finally {
   
            // 在 finally 块中抛出另一个异常
            throw new RuntimeException("finally 块中的异常");
        }
    }
}

在这个示例中,try 块中抛出了一个 RuntimeException,但在 finally 块中又抛出了另一个 RuntimeException。由于 finally 块中的代码无论如何都会执行,并且 finally 块中的异常会覆盖 try 块中的异常,导致原始异常信息丢失。

4.2.2 在 catch 块中抛出异常
public class ExceptionLossInCatch {
   
    public static void main(String[] args) {
   
        try {
   
            // 模拟抛出异常
            throw new RuntimeException("原始异常");
        } catch (RuntimeException e) {
   
            // 在 catch 块中抛出另一个异常
            throw new 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AI_DL_CODE

您的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值