秒懂Java异常

124 阅读3分钟

一、什么是Java异常

在Java中,异常是程序运行过程中出现的错误。根据错误的严重程度,Java将异常分为两类:Error(错误)Exception(异常)

  • Error(错误):

    • 这些是严重的问题,如内存溢出或JVM(Java虚拟机)故障。这类错误一般无法由程序自行处理,只能通过系统干预。
  • Exception(异常):

    • 这是在程序运行中可以预料到的问题,如空指针异常或数组越界。这些异常可以被程序捕获并处理,避免程序崩溃。

1.1 编译异常和运行时异常

  • 编译异常(受检异常):

    • 这是在编译阶段必须处理的异常,如文件未找到或数据库连接失败。Java要求你必须处理这些异常,确保程序的健壮性。
  • 运行时异常(非受检异常):

    • 这是在程序运行期间可能发生的异常,如空指针异常或算术异常。这类异常通常由代码错误引起,不需要强制捕获。

二、处理异常的方式

在Java中,我们通常使用try-catch块来捕获和处理异常。

try {
    // 业务逻辑
} catch (Exception e) {
    // 捕获异常后的处理逻辑
}

你可以根据不同类型的异常,进行更细致的捕获:

try {
    // 业务逻辑
} catch (IOException ie) {
    // 捕获IO异常的处理逻辑
} catch (Exception e) {
    // 捕获其他异常的处理逻辑
}

三、throw 抛出异常

我们可以使用throw语句主动抛出异常来控制程序的执行流程:

public UserVO queryUser(Long id) {
    UserDO userDO = userMapper.queryUserById(id);
    if (userDO == null) {
        throw new RuntimeException("用户不存在");
    }
    return userDO.toVo();
}

为更精确地表示不同的错误类型,我们可以自定义异常类:

public UserVO queryUser(Long id) {
    UserDO userDO = userMapper.queryUserById(id);
    if (userDO == null) {
        throw new UserNotFoundException();
    }
    if (!checkLicence(userDO)) {
        throw new BadLicenceException();
    }
    return userDO.toVo();
}

四、如何优雅地抛出异常

4.1 使用断言增加代码的可读性

我们可以使用断言(assert)来替代简单的if-throw结构:

public UserVO queryUser(Long id) {
    UserDO userDO = userMapper.queryUserById(id);
    Assert.notNull(userDO, "用户不存在");
    return userDO.toVo();
}

4.2 自定义断言

通过自定义断言,我们可以在断言失败时抛出自定义异常:

  • 自定义异常类:
public class BusinessException extends BaseException {
    public BusinessException(IResponseEnum responseEnum, Object[] args, String msg) {
        super(msg, responseEnum, args);
    }
}
  • 自定义断言接口:
public interface BusinessExceptionAssert extends IResponseEnum, MyAssert {
    @Override
    default BaseException newException(Object... args) {
        return new BusinessException(this, args, this.getMsg());
    }
}
  • 使用枚举定义异常类型:
public enum ResponseEnum implements IResponseEnum, BusinessExceptionAssert {
    USER_NOT_FOUND("1020", "用户不存在");
    private final String code;
    private final String msg;

    ResponseEnum(String code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    @Override
    public String getCode() {
        return code;
    }

    @Override
    public String getMsg() {
        return msg;
    }
}
  • 使用自定义断言:
public UserVO queryUser(Long id) {
    UserDO userDO = userMapper.queryUserById(id);
    ResponseEnum.USER_NOT_FOUND.assertNotNull(userDO);
    return userDO.toVo();
}

五、统一处理异常

在代码的网关处,我们可以统一处理异常,确保系统的稳定性:

@ControllerAdvice
public class BusinessExceptionHandler {
    @ExceptionHandler(value = BusinessException.class)
    @ResponseBody
    public Response handleBusinessException(BaseException e) {
        return new Response(e.getResponseEnum().getCode(), e.getResponseEnum().getMsg());
    }
}

这种方式结合了自定义断言的高可读性和自定义异常的精确性,使得代码更简洁易读,同时提高了异常处理的灵活性。