重生之小比特头像
关注
【Java SE】异常封面图

【Java SE】异常

目录

本节学习目标

1. 异常的概念与体系结构

1.1 什么是异常

1.2 异常继承体系

2. 异常处理 5 大关键字

2.1 throw:手动抛出异常对象(写在方法体内)

2.2 throws:方法声明上声明异常(写在方法签名后)

2.3 try‑catch 捕获处理异常

try‑catch 重要坑点与规则

2.4 finally 代码块

高频面试题:try 里面 return,finally 还会执行吗?

2.5 异常完整向上传递流程(调用栈)

3. 自定义异常

课后选择题

📔本篇核心考点总结


本节学习目标

  1. 理解异常概念、Throwable完整继承体系,分清Error和Exception
  2. 区分受检异常(编译时异常)、非受检异常(运行时异常 RuntimeException)
  3. 掌握五大关键字:throw、throws、try‑catch、finally
  4. 吃透异常向上传递调用栈、完整异常处理流程
  5. 学会编写自定义异常,区分受检、非受检自定义异常
  6. 熟悉面试高频坑点:finally与return共存的执行逻辑

1. 异常的概念与体系结构

1.1 什么是异常

异常:程序运行过程中发生非正常错误状态,打断正常执行流程,可以通过 Java 代码捕获处理。

注意:编译语法错误(比如单词拼写错)不属于异常,编译直接失败,根本不会生成 class 字节码。

常见运行异常示例:

  1. 算术异常 ArithmeticExceptio
  2. 数组下标越界 ArrayIndexOutOfBoundsException
public static void main(String[] args) {
    int[] array = {1, 2, 3};
    System.out.println(array[100]);
}
  1. 空指针异常 NullPointerException
public static void main(String[] args) {
    int[] array = null;
    System.out.println(array.length);
}

1.2 异常继承体系

顶层父类:Throwable,派生出两大分支:Error、Exception。

  1. Error(错误) JVM 层面严重错误,程序代码无法处理,无力回天。 典型例子:
  • StackOverflowError 栈溢出(递归太深)
  • OutOfMemoryError 内存耗尽。

出现 Error,程序只能终止,写 try‑catch 也救不了。

  1. Exception(异常) 程序员可以编写代码捕获、修复,让程序继续运行。分为两大类:
  • 受检异常(Checked Exception / 编译时异常):除RuntimeException之外的 Exception 子类。编译阶段编译器强制要求处理,要么try‑catch捕获,要么throws声明抛出。
  • 非受检异常(Unchecked Exception / 运行时异常):RuntimeException以及它的所有子类。编译不强制处理,运行才会报错。

常见运行时异常:NullPointerException、ArrayIndexOutOfBoundsException、ArithmeticException、ClassCastException。

示例:受检异常编译报错

class Person{
    @Override
    public Person clone() {
        // CloneNotSupportedException是受检异常,编译器强制处理
        return (Person)super.clone();
    }
}
//编译报错:未报告的异常错误java.lang.CloneNotSupportedException; 必须对其进行捕获或声明以便抛出

2. 异常处理 5 大关键字

throw、throws、try、catch、finally

2.1 throw:手动抛出异常对象(写在方法体内)

作用:代码内部检测到错误,手动创建异常对象抛出去,通知方法调用者。

语法:

throw new XXXException("异常描述信息");

示例:获取数组指定下标元素,参数合法性校验

public static int getElement(int[] array, int index){
    if(null == array){
        throw new NullPointerException("传递的数组为null");
    }
    if(index < 0 || index >= array.length){
        throw new ArrayIndexOutOfBoundsException("传递的数组下标越界");
    }
    return array[index];
}

public static void main(String[] args) {
    int[] array = {1,2,3};
    getElement(array, 3);
}

throw 注意事项:

  1. throw只能写在方法体内部。
  2. 抛出必须是Exception或者它的子类实例对象。
  3. 如果抛出运行时异常(RuntimeException),编译器不强制处理,直接交给上层调用者。
  4. 如果抛出受检异常,必须try‑catch捕获,或者方法上写throws声明,否则编译报错。
  5. throw执行之后,本行后面的代码不会执行。

2.2 throws:方法声明上声明异常(写在方法签名后)

throw是抛出异常对象;throws是告诉调用者:本方法有可能产生哪些异常,把处理责任交给调用方。

语法:

修饰符 返回类型 方法名(参数列表) throws 异常1,异常2{
}

示例:打开配置文件,文件找不到是受检异常

import java.io.FileNotFoundException;

public class Config {
    //声明该方法可能抛出FileNotFoundException
    public void openConfig(String filename) throws FileNotFoundException{
        if(!filename.equals("config.ini")){
            throw new FileNotFoundException("配置文件名字不对");
        }
    }
}

throws 重点规则:

  1. 写在方法参数列表圆括号之后。
  2. 声明的类型必须是Exception或者子类。
  3. 方法内部抛出多个异常,可以逗号隔开写多个;如果异常存在父子继承关系,可以直接写父类异常,简化代码。
import java.io.IOException;

public class Config {
    //FileNotFoundException是IOException子类,直接声明父类IOException即可
    public void openConfig(String filename) throws IOException{
        throw new FileNotFoundException("配置文件名字不对");
    }
}
  1. 调用带有throws声明受检异常的方法,调用者二选一:
    • 方案①:调用处使用try‑catch捕获处理;
    • 方案②:调用者方法继续追加throws,继续往上抛。
//方案2 main继续往上抛,异常交给JVM
public static void main(String[] args) throws IOException {
    Config config = new Config();
    config.openConfig("config.ini");
}

IDEA 快捷键:光标放在报错代码上,Alt+Insert快速选择:①添加 throws 声明;②生成 try‑catch 包裹代码。

2.3 try‑catch 捕获处理异常

throws只是向外通告异常,不会真正解决问题;try‑catch 才是真正处理异常。

完整语法格式

try{
    // 有可能发生异常的业务代码
}catch(异常类型 异常对象){
    //匹配该异常之后执行处理逻辑
}catch(异常类型 异常对象){
    //多种异常对应多个catch块
}finally{
    //无论是否发生异常,这里代码一定会执行(除极端JVM退出)
}
// try‑catch之后的代码,如果异常被捕获处理,会正常执行

示例:捕获文件找不到异常

import java.io.FileNotFoundException;
public class Config {
    public void openConfig(String filename) throws FileNotFoundException{
        if(!filename.equals("config.ini")){
            throw new FileNotFoundException("配置文件名字不对");
        }
    }

    public static void main(String[] args) {
        Config config = new Config();
        try {
            config.openConfig("config.txt");
            System.out.println("文件打开成功");
        } catch (FileNotFoundException e) {
            //打印异常信息三种方式
            System.out.println(e.getMessage()); //只获取异常描述文字
            System.out.println(e); //异常类名 + 异常信息
            e.printStackTrace(); //⭐最常用:打印完整调用栈信息,定位出错行号
        }
        //异常被捕获处理,后续代码继续执行
        System.out.println("异常处理完毕,程序继续往下跑");
    }
}
try‑catch 重要坑点与规则
  1. try 块中,一旦某行抛出异常,这行后面 try 内剩余代码不再执行,直接跳到 catch 匹配。
  2. catch 是按异常类型匹配,抛出异常和 catch 声明类型一致、或是它的子类,才会被捕获。类型不匹配则不会捕获,异常继续向上抛出。

❌错误示例:异常类型不匹配,捕获失效

public static void main(String[] args) {
    try {
        int[] array = {1,2,3};
        System.out.println(array[3]); //数组越界异常
    } catch (NullPointerException e) { //捕获空指针,类型对不上
        e.printStackTrace();
    }
    System.out.println("后续代码"); //不会打印,异常交给JVM程序终止
}
  1. 多个异常分开多个 catch 块;子类异常 catch 必须写在父类异常前面!

如果父类 Exception 写在前边,后面子类 catch 块永远执行不到,编译报错。

✅正确顺序:子类在前,父类在后

public static void main(String[] args) {
    int[] array = {1, 2, 3};
    try {
        System.out.println(array[100]);
    } catch (ArrayIndexOutOfBoundsException e) {
        System.out.println("数组下标越界");
        e.printStackTrace();
    } catch (NullPointerException e) {
        System.out.println("空指针异常");
        e.printStackTrace();
    }
    System.out.println("try‑catch执行结束");
}
  1. 多个异常处理逻辑完全一样,可以使用|竖线合并同一个 catch
catch (ArrayIndexOutOfBoundsException | NullPointerException e) {
    e.printStackTrace();
}
  1. 直接捕获大父类Exception可以捕获全部异常,业务开发不推荐,会掩盖真实错误,调试不方便。
//不推荐,一网打尽,分不清是什么错误
try{

}catch(Exception e){
    e.printStackTrace();
}

2.4 finally 代码块

finally:无论是否抛出异常,绝大多数场景一定会执行。 核心用途:资源释放!关闭文件流、Scanner、数据库连接、网络 socket。 因为 try 里面一旦 return,try 后面普通代码不会跑,但是 finally 依旧执行。

示例场景:Scanner 资源释放

import java.util.InputMismatchException;
import java.util.Scanner;

public class TestFinally {
    public static int getData(){
        Scanner sc = null;
        try{
            sc = new Scanner(System.in);
            int data = sc.nextInt();
            return data; //这里return,方法准备返回
        }catch (InputMismatchException e){
            e.printStackTrace();
        }finally {
            //不管正常return,还是异常,这里都会执行,用来关闭资源
            System.out.println("finally 执行,释放资源");
            if(sc != null){
                sc.close();
            }
        }
        return 0;
    }

    public static void main(String[] args) {
        int num = getData();
        System.out.println(num);
    }
}
高频面试题:try 里面 return,finally 还会执行吗?

finally 在方法真正 return 返回之前执行。 ⚠️强烈禁止在 finally 里面写 return!会直接覆盖 try/catch 中的 return 返回值。

public static int func() {
    try {
        return 10;
    } finally {
        //禁止在这里写return!
        return 20;
    }
}
public static void main(String[] args) {
    System.out.println(func()); //输出20,try的return 10被覆盖
}

开发规范:finally只做资源清理,不要写 return,编译器会报警告。

2.5 异常完整向上传递流程(调用栈)

  1. JVM 有虚拟机栈,记录每一层方法调用关系,称为调用栈。
  2. 当前方法抛出异常,如果本层没有合适 catch 捕获,异常顺着调用栈向上一层调用方法传递。
  3. 一层层向上传递,如果到 main 方法依旧没有捕获,交给 JVM 处理:打印异常栈信息,程序直接终止,后面代码不再运行。

示例:异常向上抛

public static void main(String[] args) {
    try {
        func();
    } catch (ArrayIndexOutOfBoundsException e) {
        e.printStackTrace();
    }
    System.out.println("main后续代码,可以执行");
}

public static void func() {
    int[] array = {1, 2, 3};
    System.out.println(array[100]); //本方法没有catch,向上抛给main
}

完整流程总结:

  1. 执行 try 里面代码;
  2. try 内部出现异常,立刻终止 try 剩余代码;匹配当前方法 catch;
  3. 匹配成功,执行 catch;匹配失败,异常向上层调用者抛出;
  4. 无论是否捕获异常,finally 块在本方法结束前一定执行;
  5. 持续向上传递,如果 main 方法也没有处理,JVM 接管,程序终止。

3. 自定义异常

Java 内置异常不能覆盖全部业务场景,比如登录业务:用户名错误、密码错误,需要业务自定义异常。

自定义异常规则:

  1. 继承Exception → 受检异常(编译强制处理)
  2. 继承RuntimeException → 非受检运行时异常,编译不强制处理
  3. 必须实现一个带 String message 参数的构造方法,调用父类 super (message) 保存异常信息。

示例:登录业务自定义异常

//用户名错误:受检异常,继承Exception
class UserNameException extends Exception {
    public UserNameException(String message) {
        super(message);
    }
}

//密码错误
class PasswordException extends Exception {
    public PasswordException(String message) {
        super(message);
    }
}

public class Login {
    private String userName = "admin";
    private String password = "123456";

    //声明可能抛出两个自定义受检异常
    public void loginInfo(String userName, String password) throws UserNameException, PasswordException{
        if (!this.userName.equals(userName)) {
            throw new UserNameException("用户名错误!");
        }
        if (!this.password.equals(password)) {
            throw new PasswordException("密码错误!");
        }
        System.out.println("登陆成功");
    }

    public static void main(String[] args) {
        Login login = new Login();
        try {
            login.loginInfo("admin111", "123456");
        } catch (UserNameException e) {
            e.printStackTrace();
        } catch (PasswordException e) {
            e.printStackTrace();
        }
    }
}

开发选择经验:

  • 业务错误希望编译阶段强制提醒调用方处理:继承Exception受检异常。
  • 属于程序 bug(参数非法),不需要编译强制检查:继承RuntimeException运行时异常。

课后选择题

答案 1.C    2.AD      3.D

📔本篇核心考点总结

  1. Throwable分两大分支:Error虚拟机严重错误无法处理;Exception程序员可处理异常。
  2. 受检异常(Checked):除 RuntimeException 以外的 Exception 子类,编译强制处理;运行时异常 RuntimeException 子类,编译不强制处理。
  3. throw:方法体内手动抛出异常对象;throws写在方法签名,声明该方法可能抛出的异常,把责任交给调用者。
  4. try‑catch真正捕获处理异常;catch 捕获匹配看类型;子类异常 catch 必须写在父类前面。
  5. finally主要用于资源释放,绝大多数场景必执行;禁止在 finally 写 return,会覆盖原有返回值。
  6. 异常没有被当前 catch 捕获,沿着调用栈向上传递;main 也不处理交给 JVM,程序终止。
  7. 自定义异常:继承Exception是受检;继承RuntimeException是非受检;需要带 message 构造。

面试简答 Q:throw 和 throws 区别?

A:throw 在方法内部,手动抛出一个异常实例对象;throws 写在方法声明之后,向外声明本方法可能抛出哪些异常,提醒调用者处理。

Q:finally 一定会执行吗?什么情况不执行?

A:绝大多数情况会执行;只有System.exit(0)直接终止 JVM 虚拟机的时候,finally 不会跑。

Q:受检异常和非受检异常区别?

A:受检异常编译阶段强制 try‑catch 或者 throws;RuntimeException 运行时异常编译不强制处理,运行时才报错。

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/wuqingyigexiaoba/article/details/165751455

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--