JavaSE基础-异常类-Exception类和自定义异常类

一、Java 异常的继承体系

Java 中的异常体系是一个树形结构,所有异常类最终都继承自java.lang.Throwable。这个体系主要分为三大类:

异常继承关系示意:
Object
└── Throwable(所有异常可以抛出)
    ├── Error(系统错误,不可处理)
    └── Exception(程序可处理的异常)
        ├── RuntimeException(运行时异常,非受检异常。子类无需处理异常,异常概率低)
        └── 其他Exception子类(编译时异常,受检异常。子类需处理异常)

1. Throwable

所有异常和错误的基类,任何可以被抛出的类都必须继承自Throwable

2. Error

表示系统级错误,通常是 JVM 内部问题(如内存溢出、栈溢出),程序无法处理,只能退出 JVM。例如:

  • OutOfMemoryError:内存不足
  • StackOverflowError:栈溢出(递归过深)
  • VirtualMachineError:JVM 错误

3. Exception

程序可处理的异常,又分为两类:

编译时异常(Checked Exception)
  • 必须在编译时处理(用try-catchthrows声明);
  • 继承自Exception但不继承自RuntimeException
  • 常见的如FileNotFoundExceptionIOExceptionSQLException等。
运行时异常(Unchecked Exception)
  • 编译时无需处理,程序运行时可能抛出;
  • 继承自RuntimeException
  • 常见的如NullPointerExceptionArrayIndexOutOfBoundsExceptionArithmeticException等。

二、异常处理的核心原则

1. 异常一定在运行时产生

虽然编译时异常需要在编译阶段处理,但异常对象本身是在程序运行时创建的。例如:

FileInputStream file = new FileInputStream("C:\\abc.txt");
// 当文件不存在时,运行到这行才会new一个FileNotFoundException对象

2. 编译时异常的两种处理方式

方式一:try-catch 捕获异常
public static void f3() {
    try {
        FileInputStream file = new FileInputStream("C:\\abc.txt");
        file.read();
    } catch (FileNotFoundException e) {
        System.out.println("文件不存在:" + e.getMessage());
        e.printStackTrace(); // 打印详细堆栈信息
    } catch (IOException e) {
        System.out.println("读取文件失败:" + e.getMessage());
    }
}
方式二:throws 声明异常
public static void f1() throws FileNotFoundException {
    f2(); // 调用f2,将异常抛给上层调用者
}

public static void f2() throws FileNotFoundException {
    FileInputStream file = new FileInputStream("C:\\abc.txt");
}

3. 运行时异常的处理

运行时异常编译时不强制要求处理,但在代码中应该通过逻辑判断避免。例如:

public void divide(int a, int b) {
    if (b == 0) {
        throw new ArithmeticException("除数不能为0"); // 主动抛出运行时异常
    }
    System.out.println(a / b);
}

三、try-catch 的实战技巧

1. 异常捕获顺序:从小到大写 catch 块

捕获异常时,应该先捕获子类异常,再捕获父类异常。否则,父类异常会优先捕获,导致子类异常无法被单独处理。

try {
    // 可能抛出多种异常的代码
} catch (FileNotFoundException e) { // 先捕获子类异常
    System.out.println("文件不存在");
} catch (IOException e) { // 再捕获父类异常
    System.out.println("IO操作失败");
} catch (Exception e) { // 最后捕获通用异常
    System.out.println("未知异常");
}

2. JDK 8 的多异常捕获

JDK 8 允许在一个 catch 块中捕获多个异常,用|分隔:

try {
    // 可能抛出异常的代码
} catch (FileNotFoundException | IOException e) {
    System.out.println("文件操作异常:" + e.getMessage());
}

3. try 中异常发生后的执行流程

一旦 try 块中的某行代码抛出异常,该行后面的代码不会执行,直接跳转到匹配的 catch 块:

try {
    FileInputStream file = new FileInputStream("C:\\abc.txt"); // 若此处抛出异常
    file.read(); // 这行不会执行
    System.out.println("读取成功"); // 这行也不会执行
} catch (FileNotFoundException e) {
    System.out.println("文件不存在");
}

四、finally 块的作用与注意事项

1. finally 块一定会执行

无论 try 块中是否发生异常,finally 块中的代码都会执行。通常用于释放资源(如关闭文件、数据库连接等):

public static void f4() {
    FileInputStream file = null;
    try {
        file = new FileInputStream("C:\\abc.txt");
        // 执行IO操作
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } finally {
        if (file != null) {
            try {
                file.close(); // 释放资源
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

2. finally 块的特殊情况

  • 当 try 或 catch 块中执行System.exit(0)时,finally 块不会执行;
  • 当线程被中断或终止时,finally 块可能不会执行。

3. JDK 7 的 try-with-resources 语法

对于实现了AutoCloseable接口的资源(如FileInputStreamConnection),可以使用更简洁的语法自动关闭资源:

public static void f5() {
    try (FileInputStream file = new FileInputStream("C:\\abc.txt")) {
        // 执行IO操作,资源会自动关闭
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

五、自定义异常的设计与使用

在实际开发中,除了使用 Java 内置的异常,还可以自定义异常来满足业务需求。

1. 自定义编译时异常

// 继承Exception,成为编译时异常。1 继承Exception;2 写一个构造方法
public class BusinessException extends Exception {
    public BusinessException(String message) {
        super(message);
    }
}

// 使用自定义异常
public void transferMoney(double amount) throws BusinessException {
    if (amount <= 0) {
        throw new BusinessException("转账金额必须大于0");
    }
    // 转账逻辑
}

2. 自定义运行时异常

// 继承RuntimeException,成为运行时异常。1 继承RuntimeException ;2 写一个构造方法
public class UserNotFoundException extends RuntimeException {
    public UserNotFoundException(String message) {
        super(message);
    }
}

// 使用自定义异常
public User getUserById(Long id) {
    User user = userDao.findById(id);
    if (user == null) {
        throw new UserNotFoundException("用户ID不存在:" + id);
    }
    return user;
}

六、异常处理的最佳实践

1. 避免捕获通用异常

尽量避免捕获ExceptionThrowable,这会掩盖代码中的潜在问题:

try {
    // 业务逻辑
} catch (Exception e) { // 不推荐
    e.printStackTrace();
}

2. 异常信息要明确

抛出异常时,提供足够的上下文信息,方便调试:

if (file == null) {
    throw new NullPointerException("文件对象为空,无法执行操作");
}

3. 不要 “吞掉” 异常

catch 块中至少要记录日志,避免悄无声息地忽略异常:

try {
    // 可能抛出异常的代码
} catch (IOException e) {
    // 不要空实现
    // 至少记录日志:
    logger.error("IO操作失败", e);
}

4. 优先使用 try-with-resources

处理需要关闭的资源时,优先使用 try-with-resources 语法,减少代码冗余:

// 旧写法(需手动关闭)
FileInputStream file = null;
try {
    file = new FileInputStream("path");
} catch (IOException e) {
    // 处理异常
} finally {
    if (file != null) {
        try {
            file.close();
        } catch (IOException e) {
            // 处理关闭异常
        }
    }
}

// 新写法(自动关闭)
try (FileInputStream file = new FileInputStream("path")) {
    // 使用资源
} catch (IOException e) {
    // 处理异常
}

七、总结

在实际开发中,要根据场景选择合适的异常处理方式:对于编译时异常,必须显式处理;对于运行时异常,要通过逻辑判断提前预防。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值