一、异常概述

1.1 什么是异常?

异常是程序在编译阶段或运行阶段出现的非正常、不符合预期的情况。Java将所有程序错误、异常情况封装成异常对象,统一管理和处理。

1.2 异常的作用

  1. 避免程序直接崩溃终止,提升程序健壮性;
  2. 精准记录报错信息,方便开发者快速定位、修复Bug;
  3. 统一错误处理机制,让程序容错、可控。

1.3 异常示例

数组下标越界异常,是最基础的运行时异常案例:

package com.lemon.test;

public class test1 {
    public static void main(String[] args) {
        // 数组长度为3,合法下标:0、1、2
        int[] nums = {1,2,3};
        // 访问下标10,超出数组长度,触发异常
        System.out.println(nums[10]);
    }
}

终端显示异常信息:
在这里插入图片描述

报错结果说明:程序抛出 ArrayIndexOutOfBoundsException 数组下标越界异常,精准定位到报错代码行,程序直接终止运行。

二、异常整体体系结构

Java所有错误和异常的顶层父类是 Throwable,分为两大分支:Error(系统错误)、Exception(程序异常)。
在这里插入图片描述

2.1 Error 系统级错误

核心特点:JVM底层、系统层面的致命问题,代码无法捕获、无法修复,一旦出现程序直接崩溃。
常见类型:

  • VirtualMachineError:虚拟机错误(内存溢出、虚拟机崩溃)
  • LinkageError:类链接错误(依赖类缺失、版本不匹配)
  • AWTError:图形界面系统级错误

2.2 Exception 程序级异常

代码逻辑或外部资源问题导致的异常,可捕获、可处理,处理后程序可正常运行。分为两大类:运行时异常、受检查异常。

2.2.1 运行时异常(RuntimeException)

特点:编译阶段不报错,运行阶段才触发,编译器不强制处理,多由代码逻辑不严谨导致。
常见运行时异常:

  • ArrayIndexOutOfBoundsException:数组下标越界
  • NullPointerException:空指针异常(访问null对象的属性/方法)
  • ClassCastException:类型强制转换异常
  • ArithmeticException:算术异常(如整数除以0)
  • IndexOutOfBoundsException:集合/数组下标越界父类异常

2.2.2 受检查异常(编译异常)

特点:编译阶段强制校验,必须手动处理(捕获/抛出),否则代码无法编译。多由外部资源、文件、网络、解析操作导致。
常见受检查异常:

  • IOException:IO流通用异常
  • FileNotFoundException:文件未找到异常
  • EOFException:文件读取结束异常
  • DateTimeParseException:时间格式解析异常
  • ParseException:通用格式解析异常

2.3 异常体系核心总结

  1. Throwable:所有错误、异常的顶层父类;
  2. Error:系统致命错误,不可处理;
  3. RuntimeException:代码逻辑问题,编译不校验,可选处理;
  4. 受检查异常:外部资源问题,编译强制校验,必须处理。

三、Java异常处理四大机制

Java异常处理的核心在于四个关键字:throwthrowstry-catchfinally。它们共同构建了Java程序的健壮性。

1. throws - 声明异常

作用:在方法签名中声明该方法可能抛出的检查型异常(Checked Exception),提示调用者需要处理这些异常。
特点:仅声明异常类型,不进行具体处理。若异常最终无人捕获,将由JVM处理并终止程序。
语法:在方法参数列表后添加 throws 异常类型1, 异常类型2

import java.io.FileNotFoundException;
import java.io.FileReader;

public class FileProcessor {
    // 声明方法可能抛出FileNotFoundException
    public static void openFile() throws FileNotFoundException {
        FileReader reader = new FileReader("data.txt"); // 可能引发文件不存在异常
    }

    public static void main(String[] args) throws FileNotFoundException {
        openFile(); // 调用者需处理或继续声明
    }
}

2. throw - 主动抛出异常

作用:在方法内部主动创建并抛出异常对象,常用于业务规则校验或强制中断执行流程。
特点:可抛出任意 Throwable 子类(包括自定义异常),用于主动控制异常流程。

public class Validator {
    public static void validateAge(int age) {
        if (age < 0 || age > 120) {
            // 手动抛出运行时异常(非检查型)
            throw new IllegalArgumentException("年龄必须在0-120之间");
        }
        System.out.println("年龄校验通过");
    }

    public static void main(String[] args) {
        validateAge(-5); // 触发异常
    }
}

3. try-catch - 捕获并处理异常

作用:主动捕获代码块中的异常,防止程序意外终止,是实际开发中最常用的处理方式。
结构

  • try:包裹可能引发异常的代码
  • catch:捕获特定类型异常并处理
  • 可多级捕获:catch (ExceptionType1 e) {...} catch (ExceptionType2 e) {...}
public class ArrayHandler {
    public static void main(String[] args) {
        int[] data = {10, 20, 30};
        try {
            System.out.println(data[5]); // 可能越界
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("错误:数组索引越界");
            e.printStackTrace(); // 打印堆栈跟踪(调试用)
        }
        System.out.println("后续代码正常执行");
    }
}

4. finally - 资源清理块

核心特性:无论是否发生异常或存在return语句,finally块中的代码必定执行(除非调用 System.exit())。
主要用途:释放资源(如文件句柄、数据库连接、网络套接字),避免资源泄漏。

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

public class ResourceManager {
    public static void main(String[] args) {
        FileReader reader = null;
        try {
            reader = new FileReader("config.txt");
            // 文件操作...
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 确保资源关闭
            try {
                if (reader != null) {
                    reader.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

关键

  1. finally 执行优先级高于 try/catch 中的 return
  2. Java 7+ 推荐使用 try-with-resources 自动关闭资源(实现 AutoCloseable 接口):
try (FileReader reader = new FileReader("file.txt")) {
    // 自动关闭资源
} catch (IOException e) {
    e.printStackTrace();
}

finally不执行的原因

  1. try 块内执行 System.exit()
    在 try 代码块中调用 System.exit(整数) 会直接终止整个 JVM,程序立刻退出,根本不会跳转执行 finally 块。
  2. try 块陷入死循环、死锁、永久阻塞
    try 里写了无终止条件的 while(true) 死循环、线程死锁、无限等待网络 / 锁资源,程序一直卡死在 try 内部,永远走不到 finally。
  3. 外部强制杀死进程、断电、系统崩溃
    手动在任务管理器 / 命令行杀掉 Java 进程、电脑突然断电、系统蓝屏崩溃,JVM 被粗暴终止,没有机会执行 finally 收尾。
  4. 调用废弃方法 Thread.stop() 暴力终止线程
    用 thread.stop() 强行杀死线程,这种暴力终止会直接中断线程执行,跳过 finally 不执行。
  5. 发生严重系统级 Error
    出现 StackOverflowError 栈溢出、OutOfMemoryError 内存溢出等严重错误,属于 JVM 级致命故障,程序直接崩溃,finally 无法执行。

四、JDK7 & JDK9 异常处理新特性

传统 try-catch-finally 存在代码冗余、资源关闭繁琐等问题。JDK7 引入了三大优化特性,JDK9 进一步升级,显著提升代码简洁性与安全性。


特性一:多异常同捕 (Multi-catch)

核心:合并相同处理逻辑的异常捕获,消除冗余 catch 块。
规则

  1. 异常类型用 | 分隔,如:catch (IOException | SQLException e)
  2. 异常类型不能有继承关系(如禁止同时捕获 Exception 和其子类);
  3. 异常对象 e 自动设置为常量(final),不可重新赋值;
  4. 仅适用于统一处理逻辑,差异处理仍需单独 catch

示例

try {
    Files.readAllBytes(Paths.get("file.txt")); // 可能抛出 IOException
    int x = 1 / 0; // 可能抛出 ArithmeticException
} catch (IOException | ArithmeticException e) {
    System.out.println("文件或运算错误");
    e.printStackTrace();
}

特性二:自动资源关闭 (try-with-resources)

核心价值:彻底替代 finally 手动关闭资源,杜绝资源泄漏。

基础用法(JDK7)
资源在 try() 中创建,作用域限于 try 块,执行完毕自动关闭:

try (FileReader reader = new FileReader("test.txt")) {
    // 使用资源
} catch (IOException e) {
    e.printStackTrace();
}

多资源管理(JDK7)
支持分号分隔多个资源,按创建逆序自动关闭

try (FileInputStream in = new FileInputStream("in.txt");
     FileOutputStream out = new FileOutputStream("out.txt")) {
    // 资源操作
} // 先关闭 out,再关闭 in

JDK9 升级
支持直接使用外部已初始化的资源变量:

FileReader reader = new FileReader("test.txt");
try (reader) { // 直接引用外部变量
    // 使用资源
} // 仍自动关闭!

优势总结

  • 代码极简,消除 finally 嵌套;
  • 自动关闭,避免遗漏或空指针;
  • 多资源逆序关闭,安全性更高;
  • JDK9 支持外部变量,灵活适配业务。

特性三:精准重抛异常 (Precise Rethrow)

核心价值:编译器智能识别 catch 块中实际抛出的异常类型,简化 throws 声明。

示例对比

// JDK7 前:需声明宽泛异常
public void read() throws Exception {
    try {
        Files.readString(Path.of("file.txt"));
    } catch (Exception e) {
        throw e; // 编译器要求 throws Exception
    }
}

// JDK7+:仅声明实际异常类型
public void read() throws IOException {
    try {
        Files.readString(Path.of("file.txt"));
    } catch (Exception e) {
        throw e; // 编译器识别实际抛出 IOException
    }
}

要点

  1. 多异常同捕:竖线分隔、禁父子类共存、异常对象常量化;
  2. 自动资源关闭
    • JDK7:try() 内创建资源,多资源逆序关闭;
    • JDK9:支持外部资源变量引用;
  3. 精准重抛:编译器动态分析异常类型,避免冗余 throws 声明。

这些特性显著提升代码可读性、健壮性和开发效率,是 Java 异常处理的里程碑式改进。


五、自定义业务异常

JDK 自带异常(如 NullPointerException)无法覆盖所有业务场景(如余额不足、用户不存在),因此需自定义异常。这有助于统一错误处理、区分系统异常(如数据库错误)和业务异常(如密码错误),便于全局异常管理和前端提示。

5.1 自定义运行时异常

自定义异常通常继承 RuntimeException(运行时异常),避免强制处理。示例:

// 自定义业务异常类
public class BusinessException extends RuntimeException {
    public BusinessException(String message) {
        super(message); // 传递错误信息
    }
}

优势:无需在方法签名声明,减少代码冗余。

5.2 业务场景使用

在业务逻辑中主动抛出自定义异常,统一处理。示例:

public class AccountService {
    // 余额校验业务逻辑
    public static void checkBalance(double balance) {
        if (balance < 10) {
            throw new BusinessException("余额不足,最低充值10元!"); // 手动抛出
        }
    }

    public static void main(String[] args) {
        try {
            checkBalance(5); // 触发异常
        } catch (BusinessException e) {
            System.out.println("业务错误: " + e.getMessage()); // 统一捕获处理
        }
    }
}

企业实践:在全局异常处理器中捕获 BusinessException,返回标准错误响应,提升可维护性。


六、异常开发规范

遵循规范可提升代码健壮性:

  1. 运行时异常处理:优先通过代码逻辑规避(如判空、下标校验),避免过度使用 try-catch。例如:

    if (str != null) { // 判空规避 NullPointerException
        str.length();
    }
    
  2. 受检查异常处理:必须处理(编译强制校验)。优先使用 try-catch 内部处理,少用 throws 上抛。例如:

    try {
        Files.readFile(path); // 受检查异常
    } catch (IOException e) {
        log.error("文件读取失败", e); // 内部处理
    }
    
  3. 禁止空 catch:捕获异常后必须写处理逻辑(如日志、恢复),禁止空块隐藏错误:

    // 错误示例:空 catch
    try { /* code */ } catch (Exception e) { }
    
    // 正确示例
    try { /* code */ } catch (Exception e) {
        log.error("异常详情", e); // 必须处理
    }
    
  4. 资源释放:IO、数据库等资源优先使用 try-with-resources,自动关闭:

    try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) {
        // 自动关闭资源
    } catch (IOException e) {
        // 处理
    }
    
  5. 业务异常自定义:统一使用自定义异常(如 BusinessException),便于全局管理和前端交互。避免混用 JDK 异常。


七、总结

Java 异常处理是面试和实战核心,总结关键点:

  1. 异常体系:继承自 Throwable,分 Error(不可处理,如内存溢出)和 Exception(可处理)。

    • Exception 分运行时异常(编译不校验,如 NullPointerException)和受检查异常(编译强制校验,如 IOException)。
  2. 四大处理机制

    • throws:声明异常。
    • throw:手动抛出异常。
    • try-catch:捕获并处理异常。
    • finally:确保资源释放(JDK7+ 使用 try-with-resources 优化)。
  3. 新特性

    • JDK7:多异常捕获(catch (Ex1 | Ex2 e))、try-with-resources。
    • JDK9:外部资源变量引用改进(如 try (resource) 支持 final 变量)。
  4. 企业开发

    • 自定义业务异常实现统一错误管理。
    • 遵循规范提升项目健壮性、可维护性和用户体验。

核心价值:异常处理不仅是语法,更是工程实践。掌握这些,能高效应对面试和实际开发挑战。


更多推荐