Java 异常体系(完整面试题 + 代码示例)

这个问题虽然看着简单,但是很多中型公司都会问,有什么很让你说出一些异常的类型,以及一些处理方式

一、什么是 Java 异常体系

简单来说,Java 异常体系是 Java 为了处理程序运行中出现的「非正常情况」(比如文件找不到、数组越界、空指针)而设计的一套标准化体系,核心是把所有异常情况归类,让开发者能清晰识别、捕获和处理这些问题。

image

(1)Error(错误)—— 程序 “救不回来” 的严重问题

  • 特点:由 JVM 抛出,属于系统级错误,程序无法捕获、也处理不了,一旦出现,JVM 大概率会终止线程 / 程序。

  • 核心逻辑:这是 “硬件 / 虚拟机层面的故障”,比如内存不够、栈溢出,开发者写代码时不用处理,也处理不了。

(2)Exception(异常)—— 程序 “能抢救” 的问题

特点: 程序运行时的业务 / 逻辑错误,开发者可以通过代码捕获并处理,让程序继续运行。

细分两类:

  • Checked 异常(受检异常):编译时就要求必须处理(要么catch捕获,要么throws声明抛出),不处理代码编译不过。比如读取文件时的FileNotFoundException。

  • Unchecked 异常(非受检异常):编译时不要求处理,运行时才会抛出,继承自RuntimeException。比如空指针、数组越界,是开发者代码写得不够严谨导致的。

类型 继承关系 编译时是否要求处理 能否被程序处理 典型例子
Error 直接继承 Throwable StackOverflowError、OOM
Checked 异常 继承 Exception(非 Runtime) IOException、SQLException
Unchecked 异常 继承 RuntimeException NullPointerException、数组越界
import java.io.FileInputStream;
import java.io.IOException;

public class ExceptionStructureDemo {
    public static void main(String[] args) {
        // 1. Unchecked异常(运行时抛出)
        String str = null;
        // str.length(); // 运行时抛NullPointerException

        // 2. Checked异常(编译期必须处理)
        try {
            FileInputStream fis = new FileInputStream("test.txt");
            fis.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

2. throw 和 throws 的区别是什么?各自的使用场景?

throw:

1.用在方法内部,手动抛出一个异常对象

2.语法:throw new ExceptionType("异常信息");

3.场景:主动触发业务异常(如参数不合法)

throws:

1.用在方法签名,声明该方法可能抛出的异常类型

2.语法:public void method() throws ExceptionType1, ExceptionType2 {}

3.场景:将异常向上抛出,由调用方处理

public class ThrowThrowsDemo {
    public static void main(String[] args) {
        try {
            checkAge(15); // 调用方必须处理声明的异常
        } catch (IllegalArgumentException e) {
            System.out.println(e.getMessage());
        }
    }

    // 方法声明可能抛出的异常(Unchecked异常可省略throws)
    public static void checkAge(int age) throws IllegalArgumentException {
        if (age < 18) {
            // 手动抛出异常对象
            throw new IllegalArgumentException("年龄必须≥18岁,当前:" + age);
        }
        System.out.println("年龄验证通过");
    }
}

3. finally 代码块一定会执行吗?哪些场景下 finally 不会执行?

通常情况: finally 一定会执行,无论 try 正常结束还是 catch 捕获异常,用于释放资源(如关闭流)

不会执行的场景:

1.调用 System.exit(0) 终止 JVM

2.执行 finally 前线程被中断 / 杀死

3.死循环 / 无限递归导致程序无法进入 finally

public class FinallyDemo {
    public static void main(String[] args) {
        System.out.println(testFinally()); // 输出20(finally修改num不影响返回值)
        // System.exit(0); // 若在此处调用,finally不会执行
    }

    public static int testFinally() {
        int num = 10;
        try {
            num /= 0; // 触发算术异常
            return num;
        } catch (ArithmeticException e) {
            num = 20;
            return num; // 先暂存返回值,再执行finally
        } finally {
            num = 30;
            System.out.println("finally执行,num=" + num);
            // return num; // 若finally有return,会覆盖try/catch的返回值(不推荐)
        }
    }
}

4. 重写方法时,异常声明有什么限制?

子类重写父类方法时,不能抛出比父类更宽泛的异常:

1.若父类方法未声明异常,子类重写时也不能声明 Checked 异常(可抛 Unchecked 异常)

2.若父类方法声明了异常,子类重写时只能抛出相同或其子类的 Checked 异常

3.Unchecked 异常(RuntimeException/Error)不受此限制,可随意抛出

class Parent {
    public void readFile() throws Exception {
        // 父类声明抛出Exception
    }
}

class Child extends Parent {
    // ✅ 合法:抛出父类异常的子类
    @Override
    public void readFile() throws IOException {
        super.readFile();
    }

    // ❌ 非法:抛出更宽泛的异常(Throwable比Exception宽泛)
    // public void readFile() throws Throwable {}

    // ✅ 合法:Unchecked异常不受限制
    public void readFile() throws NullPointerException {}
}

5. 自定义异常的实现方式?为什么要自定义异常?

实现方式:

1.继承 Exception(自定义 Checked 异常)或 RuntimeException(自定义 Unchecked 异常)

2.提供无参构造、带消息的构造、带消息 + 原因的构造

为什么要自定义:

1.区分业务异常与系统异常,便于定位问题

2.携带业务上下文信息(如用户 ID、错误码)

3.统一异常处理逻辑(如全局异常拦截)


// 自定义Checked异常
class UserNotFoundException extends Exception {
    public UserNotFoundException() {
        super();
    }

    public UserNotFoundException(String message) {
        super(message);
    }

    public UserNotFoundException(String message, Throwable cause) {
        super(message, cause);
    }
}

// 使用自定义异常
public class CustomExceptionDemo {
    public static void main(String[] args) {
        try {
            findUserById(999);
        } catch (UserNotFoundException e) {
            System.out.println("业务异常:" + e.getMessage());
            e.printStackTrace();
        }
    }

    public static void findUserById(int id) throws UserNotFoundException {
        if (id < 1 || id > 100) {
            throw new UserNotFoundException("用户ID不存在:" + id);
        }
        System.out.println("找到用户:" + id);
    }
}
posted @ 2026-03-13 10:20  白鹿为溪  阅读(46)  评论(0)    收藏  举报