12异常知识点

异常

1. 异常概念与体系结构

1.1 异常的概念

概念:程序执行过程中发生的不正常行为

常见异常示例

复制代码
// 1. 算术异常
System.out.println(10 / 0);  // ArithmeticException: / by zero

// 2. 数组越界异常
int[] arr = {1, 2, 3};
System.out.println(arr[100]);  // ArrayIndexOutOfBoundsException

// 3. 空指针异常
int[] arr2 = null;
System.out.println(arr2.length);  // NullPointerException

注意

  • 编译错误(语法错误)不是异常

  • 异常发生在运行时


1.2 异常体系结构

复制代码
Throwable
├── Error(错误,JVM无法处理)
│   ├── StackOverflowError
│   └── OutOfMemoryError
│
└── Exception(异常,程序员可处理)
    ├── RuntimeException(运行时异常/非受查异常)
    └── 其他Exception(编译时异常/受查异常)

注意

  • Error:严重问题,程序无法恢复

  • Exception:可通过代码处理


1.3 异常的分类

1. 编译时异常(Checked Exception)
复制代码
// CloneNotSupportedException是编译时异常
@Override
public Person clone() throws CloneNotSupportedException {
    return (Person)super.clone();  // 必须处理异常
}

特点

  • 编译时检查

  • 必须处理(try-catch或throws)

2. 运行时异常(RuntimeException/Unchecked Exception)
复制代码
// 常见运行时异常
int[] arr = {1, 2, 3};
System.out.println(arr[3]);  // 运行时才抛出异常

常见运行时异常

  • NullPointerException

  • ArrayIndexOutOfBoundsException

  • ArithmeticException

  • ClassCastException

  • IllegalArgumentException

注意:RuntimeException及其子类都是运行时异常


2. 异常的处理

2.1 防御式编程

1. LBYL(事前防御)
复制代码
boolean ret = login();
if (!ret) {
    // 处理登录错误
    return;
}
ret = startMatch();
if (!ret) {
    // 处理匹配错误
    return;
}
// ...更多检查

缺点:正常流程和错误处理混在一起

2. EAFP(事后处理)
复制代码
try {
    login();
    startMatch();
    // 正常流程
} catch (LoginException e) {
    // 处理登录异常
} catch (MatchException e) {
    // 处理匹配异常
}

优点:流程清晰,Java主要采用这种方式


2.2 抛出异常(throw)

概念:主动抛出异常对象

语法

复制代码
throw new XXXException("异常原因");

示例

复制代码
public static int getElement(int[] array, int index) {
    if (array == null) {
        throw new NullPointerException("数组为null");
    }
    if (index < 0 || index >= array.length) {
        throw new ArrayIndexOutOfBoundsException("索引越界: " + index);
    }
    return array[index];
}

注意易错点

  1. throw必须在方法体内

  2. 只能抛出Throwable或其子类对象

  3. 抛出RuntimeException可不处理

  4. 抛出编译时异常必须处理

  5. throw后代码不会执行


2.3 异常声明(throws)

概念:声明方法可能抛出的异常,让调用者处理

语法

复制代码
修饰符 返回值类型 方法名(参数) throws 异常类型1, 异常类型2...

示例

复制代码
public void readFile(String filename) throws FileNotFoundException, IOException {
    if (!filename.endsWith(".txt")) {
        throw new IOException("不是文本文件");
    }
    if (!new File(filename).exists()) {
        throw new FileNotFoundException("文件不存在");
    }
    // 读取文件...
}

注意易错点

  1. 跟在参数列表后

  2. 可声明多个异常,用逗号分隔

  3. 有父子关系时,声明父类即可

  4. 调用者必须处理或继续throws

快捷键:Alt + Enter 快速处理异常


2.4 捕获异常(try-catch)

概念:捕获并处理异常

语法

复制代码
try {
    // 可能出错的代码
} catch (异常类型1 e) {
    // 处理异常1
} catch (异常类型2 e) {
    // 处理异常2
} finally {
    // 总会执行的代码
}

示例

复制代码
try {
    int[] arr = {1, 2, 3};
    System.out.println(arr[3]);
} catch (ArrayIndexOutOfBoundsException e) {
    // 处理方式1:打印信息
    System.out.println("错误信息: " + e.getMessage());
    
    // 处理方式2:简单输出
    System.out.println(e);
    
    // 处理方式3:完整堆栈跟踪
    e.printStackTrace();
} finally {
    System.out.println("finally块总执行");
}
System.out.println("异常处理后继续执行");

处理方式

  • 让程序崩溃(严重问题,如支付错误)

  • 记录日志(一般问题,发报警给程序员)

  • 重试操作(网络问题等可恢复问题)

注意易错点

1. 异常类型不匹配
复制代码
try {
    int[] arr = {1, 2, 3};
    System.out.println(arr[3]);  // 抛出ArrayIndexOutOfBoundsException
} catch (NullPointerException e) {  // 类型不匹配,捕获失败
    e.printStackTrace();
}
// 异常继续向外抛出,程序终止
2. 多异常处理
复制代码
// 方式1:多个catch块
try {
    // 可能抛出多种异常
} catch (ArrayIndexOutOfBoundsException e) {
    // 处理数组越界
} catch (NullPointerException e) {
    // 处理空指针
} catch (Exception e) {  // 父类放最后
    // 处理其他异常
}

// 方式2:合并catch(JDK7+)
try {
    // ...
} catch (ArrayIndexOutOfBoundsException | NullPointerException e) {
    // 处理两种异常
}

// 错误:父类在前,子类永远执行不到
try {
    // ...
} catch (Exception e) {  // 父类
    // 这里会捕获所有异常
} catch (NullPointerException e) {  // 子类,编译错误!
    // 永远执行不到
}
3. finally的特殊情况
复制代码
public static int testFinally() {
    try {
        return 10;
    } finally {
        return 20;  // finally的return会覆盖try的return
    }
}
// 返回20,不是10!

finally面试题

复制代码
// 问题1:下面代码输出什么?
public static int test1() {
    try {
        return func();
    } finally {
        System.out.println("finally");
    }
}
// 先执行finally,再返回func()的值

// 问题2:finally一定会执行吗?
// 答:几乎总是执行,除非:
// 1. System.exit(0)退出程序
// 2. 断电、系统崩溃
// 3. 守护线程被结束

3. 异常处理流程

完整流程

  1. 执行try中代码

  2. 如果发生异常,跳转到匹配的catch

  3. 执行匹配的catch块

  4. 执行finally块

  5. 继续执行后续代码

调用栈传递

复制代码
public class Test {
    public static void main(String[] args) {
        try {
            method1();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    static void method1() {
        method2();
    }
    
    static void method2() {
        int[] arr = {1, 2, 3};
        System.out.println(arr[3]);  // 异常从这里抛出
    }
}

输出堆栈跟踪

复制代码
java.lang.ArrayIndexOutOfBoundsException: 3
    at Test.method2(Test.java:18)  ← 异常发生位置
    at Test.method1(Test.java:13)  ← 调用链
    at Test.main(Test.java:7)      ← 调用链

注意:异常会沿着调用栈向上传递,直到被捕获或到达JVM


4. 自定义异常

4.1 创建自定义异常

复制代码
// 1. 继承Exception(编译时异常)
class MyCheckedException extends Exception {
    public MyCheckedException(String message) {
        super(message);
    }
    
    public MyCheckedException(String message, Throwable cause) {
        super(message, cause);
    }
}

// 2. 继承RuntimeException(运行时异常)
class MyRuntimeException extends RuntimeException {
    public MyRuntimeException(String message) {
        super(message);
    }
}

4.2 使用示例:用户登录

复制代码
// 1. 定义异常类
class UsernameException extends Exception {
    public UsernameException(String message) {
        super(message);
    }
}

class PasswordException extends Exception {
    public PasswordException(String message) {
        super(message);
    }
}

// 2. 业务类
class LoginService {
    private String correctUsername = "admin";
    private String correctPassword = "123456";
    
    public void login(String username, String password) 
            throws UsernameException, PasswordException {
        if (!correctUsername.equals(username)) {
            throw new UsernameException("用户名错误: " + username);
        }
        if (!correctPassword.equals(password)) {
            throw new PasswordException("密码错误");
        }
        System.out.println("登录成功!");
    }
}

// 3. 使用
public class Test {
    public static void main(String[] args) {
        LoginService service = new LoginService();
        
        try {
            service.login("admin", "wrong");
        } catch (UsernameException e) {
            System.out.println("用户名异常: " + e.getMessage());
        } catch (PasswordException e) {
            System.out.println("密码异常: " + e.getMessage());
        } finally {
            System.out.println("登录流程结束");
        }
    }
}

最佳实践

  1. 提供有意义的异常信息

  2. 可添加额外字段存储上下文信息

  3. 考虑异常链(cause参数)

  4. 覆盖toString()方便调试


5. 面试常见问题

1. throw vs throws

区别 throw throws
位置 方法体内 方法声明处
数量 一次抛出一个异常 可声明多个异常
作用 主动抛出异常对象 声明可能抛出的异常类型

2. finally执行时机

复制代码
public static int test() {
    try {
        System.out.println("try");
        return 1;          // 1. 计算返回值
    } finally {
        System.out.println("finally");  // 2. 执行finally
        return 2;           // 3. 覆盖返回值
    }
    // 返回2
}

3. 异常处理原则

  1. 具体明确:捕获具体异常,不要用Exception一把抓

  2. 及时处理:不要吞掉异常(空catch块)

  3. 记录日志:记录异常上下文信息

  4. 资源释放:在finally中释放资源

  5. 异常转换:底层异常转换为业务异常

4. 资源管理新方式(try-with-resources)

复制代码
// JDK7+ 自动关闭资源
try (FileInputStream fis = new FileInputStream("test.txt");
     BufferedReader br = new BufferedReader(new InputStreamReader(fis))) {
    String line;
    while ((line = br.readLine()) != null) {
        System.out.println(line);
    }
} catch (IOException e) {
    e.printStackTrace();
}
// 自动调用close(),相当于在finally中关闭

关键总结

  1. 异常分类

    • Error:JVM级,无法处理

    • Exception:可处理

      • RuntimeException:运行时异常

      • 其他Exception:编译时异常

  2. 处理方式

    • throw:抛出异常

    • throws:声明异常

    • try-catch-finally:捕获处理

  3. 自定义异常

    • 继承Exception或RuntimeException

    • 提供构造方法

    • 添加业务信息

  4. 最佳实践

    • 不要忽略异常

    • 使用具体异常类型

    • 清理资源用finally或try-with-resources

    • 记录完整的异常信息

相关推荐
好好研究2 小时前
MobaXterm远程连接云服务器(Centos环境)及配置jdk、Tomcat、MySQL环境
java·服务器·mysql·jdk·tomcat·aliyun服务器
让我上个超影吧2 小时前
SpringAI会话记忆实现——基于MYSQL进行存储
java·spring boot·ai
m0_748233172 小时前
Laravel vs ThinkPHP:谁更适合你?
java·开发语言
henujolly2 小时前
How do you troubleshoot a CI failure?
java·开发语言·ci/cd
菜鸟xiaowang2 小时前
lunch 不显示构建列表
java
草莓熊Lotso2 小时前
Qt 显示与输入类控件进阶:数字、进度、输入框实战攻略
java·大数据·开发语言·c++·人工智能·qt
大王10242 小时前
Commons Logging 与 SLF4J 深度解读:日志门面及实现框架全解析
java
zhangkaixuan4562 小时前
Paimon Split 机制深度解析
java·算法·数据湖·lsm-tree·paimon
Remember_9932 小时前
Spring 中 REST API 调用工具对比:RestTemplate vs OpenFeign
java·网络·后端·算法·spring·php