Java中的异常

一.异常基础概念

异常是程序执行过程中出现的打断正常流程的事件。

Java通过面向对象的方式处理异常

每个异常都是Throwable(可抛出的)类或其子类的对象

二.异常类层次结构

复制代码
java.lang.Object
    └── java.lang.Throwable
            ├── java.lang.Error (错误)
            └── java.lang.Exception (异常)
                    ├── RuntimeException (运行时异常/非受检异常)
                    └── 其他Exception (受检异常)

Java所有异常都继承自Throwable类

1.Error(错误)

表示严重问题,代表系统级别的错误,通常由JVM抛出

系统一旦出现问题,sun公司会把这些错误封装成Error对象。Error是sun公司自己用的,而不是给程序员用的。因此开发人员不用管它。

2.Exception(异常)

代表程序可能出现的问题

通常用Exception类及其子类来封装程序出现的问题

1.RuntimeException(运行时异常)

包括RuntimeException类及其子类

编译阶段不会触发,在运行时才会触发的异常(如数组索引越界异常、算术异常、空指针异常)

2.其他Exception(编译时异常)

直接继承于Exception类

编译阶段(通过javac命令将.java文件转换成.class文件的过程)就会出现的异常提醒,编译器强制要求处理(如日期解析异常)

编译阶段只会检查语法错误、性能优化

编译时异常是为了提醒程序员检查本地信息(如本地事件,本地的文件)

而运行时异常就是代码出错导致的异常

三.异常的处理方式

1.JVM默认的处理方式

把异常的名称、异常出现的原因、异常出现的位置等信息输出在控制台

异常后面的代码不会再被执行,程序终止

2.自己处理

1.try-catch-finally异常处理机制

java 复制代码
try {
    可能出现异常的代码;
} catch (异常类名 变量名) {
    异常处理的代码
} finally {
    try中无论是否发生异常都会处理的代码
}
java 复制代码
package exceptiondemo;

public class demo1 {
    public static void main(String[] args) {
        int[] arr = {1,2,3,4,5};
        try {
            System.out.println(arr[5]);
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("数组越界异常");
        }
        System.out.println("程序继续执行");
    }
}

输出:

java 复制代码
数组越界异常
程序继续执行

捕获多个异常

java 复制代码
package exceptiondemo;

public class demo1 {
    public static void main(String[] args) {
        int[] arr = {1,2,3,4,5};
        try {
            //System.out.println(arr[5]);
            System.out.println(10/0);
            String s = null;
            System.out.println(s.equals("null"));
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("数组越界异常");
        } catch (ArithmeticException e) {
            System.out.println("算术异常");
        } catch (NullPointerException e) {
            System.out.println("空指针异常");
        }
        System.out.println("程序继续执行");
    }
}

输出:

java 复制代码
算术异常
程序继续执行

父类异常必须写在子类异常后面:

java 复制代码
package exceptiondemo;

public class demo1 {
    public static void main(String[] args) {
        int[] arr = {1,2,3,4,5};
        try {
            //System.out.println(arr[5]);
            System.out.println(10/0);
            String s = null;
            System.out.println(s.equals("null"));
        } catch (Exception e) {
            System.out.println("异常");
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("数组越界异常");
        } catch (ArithmeticException e) {
            System.out.println("算术异常");
        } catch (NullPointerException e) {
            System.out.println("空指针异常");
        }
        System.out.println("程序继续执行");
    }
}

报错:java: 已捕获到异常错误java.lang.ArrayIndexOutOfBoundsException

且在IDEA中就有错误提示:xception 'java.lang.ArrayIndexOutOfBoundsException' has already been caught

java 复制代码
package exceptiondemo;

public class demo1 {
    public static void main(String[] args) {
        int[] arr = {1,2,3,4,5};
        try {
            //System.out.println(arr[5]);
            System.out.println(10/0);
            String s = null;
            System.out.println(s.equals("null"));
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("数组越界异常");
        } catch (ArithmeticException e) {
            System.out.println("算术异常");
        } catch (NullPointerException e) {
            System.out.println("空指针异常");
        } catch (Exception e) {
            System.out.println("其他异常");
        }
        System.out.println("程序继续执行");
    }
}

输出:

算术异常

程序继续执行

如果try中的异常没有被catch所捕获到,则JVM会接手来进行默认处理:

java 复制代码
package exceptiondemo;

public class demo1 {
    public static void main(String[] args) {
        int[] arr = {1,2,3,4,5};
        try {
            //System.out.println(arr[5]);
            System.out.println(10/0);
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("数组越界异常");
        } 
        System.out.println("程序继续执行");
    }
}

输出:

java 复制代码
Exception in thread "main" java.lang.ArithmeticException: / by zero
	at exceptiondemo.demo1.main(demo1.java:8)

try代码块中遇到了异常会直接跳转到catch代码块去匹配,不会执行try中异常后的代码:

java 复制代码
package exceptiondemo;

public class demo1 {
    public static void main(String[] args) {
        int[] arr = {1,2,3,4,5};
        try {
            System.out.println(arr[5]);
            System.out.println("用来测试try中的代码发生异常,异常处后面的代码是否还会执行");
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("数组越界异常");
        }
        System.out.println("程序继续执行");
    }
}

输出:

数组越界异常

程序继续执行

java 复制代码
package exceptiondemo;

public class demo1 {
    public static void main(String[] args) {
        int[] arr = {1,2,3,4,5};
        try {
            //System.out.println(arr[5]);
            System.out.println(10/0);
            System.out.println("用来测试try中的代码发生异常,异常处后面的代码是否还会执行");
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("数组越界异常");
        }
        System.out.println("程序继续执行");
    }
}

输出:

java 复制代码
Exception in thread "main" java.lang.ArithmeticException: / by zero
	at exceptiondemo.demo1.main(demo1.java:8)

2.异常的几个重要方法

|-------------------------------|-----------------------------------------------------|
| public String getMessage() | 返回此Throwable的简短字符串 |
| public String toString() | 返回此Throwable的详细信息字符串表示 |
| public void printStackTrace() | 把异常的错误信息输出在控制台,在IDEA中以红色字体进行打印,与JVM默认处理方式不同,它不会终止程序 |

java 复制代码
package exceptiondemo;

public class demo1 {
    public static void main(String[] args) {
        int[] arr = {1,2,3,4,5};
        try {
            System.out.println(arr[5]);
        } catch (ArrayIndexOutOfBoundsException e) {
            String message = e.getMessage();
            System.out.println(message);
        }
        System.out.println("程序继续执行");
    }
}

输出:

java 复制代码
Index 5 out of bounds for length 5
程序继续执行
java 复制代码
package exceptiondemo;

public class demo1 {
    public static void main(String[] args) {
        int[] arr = {1,2,3,4,5};
        try {
            System.out.println(arr[5]);
        } catch (ArrayIndexOutOfBoundsException e) {
            String str = e.toString(); // 写e.toString() 然后快捷键ctrl alt v 可以自动补齐变量初始化语句
            System.out.println(str);
        }
        System.out.println("程序继续执行");
    }
}

输出:

java 复制代码
java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 5
程序继续执行
java 复制代码
package exceptiondemo;

public class demo1 {
    public static void main(String[] args) {
        int[] arr = {1,2,3,4,5};
        try {
            System.out.println(arr[5]);
        } catch (ArrayIndexOutOfBoundsException e) {
            e.printStackTrace();
        }
        System.out.println("程序继续执行");
    }
}

输出:

java 复制代码
java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 5
	at exceptiondemo.demo1.main(demo1.java:7)
程序继续执行
java 复制代码
System.err.println("报错,报错,报错");

输出:以红色字体输出三个报错

3.抛出异常

throws和throw关键字

1.throw关键字:抛出一个具体的异常对象

作用:在方法内部主动抛出一个异常(即" 制造 "一个异常)

语法:

java 复制代码
throw new 异常类("错误信息");

位置:写在方法体内

java 复制代码
public void checkAge(int age) {
    if (age < 0) {
        // 主动抛出一个运行时异常
        throw new IllegalArgumentException("年龄不能为负数!"); // 如果运行到这一行,那么下面的代码就不会被执行了
    }
    System.out.println("年龄合法:" + age);
}

2.throws关键字:声明方法可能抛出的异常类型

作用:在方法签名中声明该方法可能会抛出哪些异常,让调用者知道并处理

语法:

java 复制代码
public void methodName(...) throws 异常类1, 异常类2 { ... }

位置:写在方法参数列表之后,方法体之前

注:若是编译时异常,则必须用throws声明;若是运行时异常,可以不声明throws

java 复制代码
import java.io.*;

public void readFile(String filename) throws IOException {
    FileReader file = new FileReader(filename); // 可能抛出 IOException
    // ... 读取文件
}

3.对比

特性 throw throws
作用 抛出一个具体的异常对象 声明方法可能抛出的异常类型
位置 方法体内 方法签名(参数后)
后面跟什么 异常对象(如 new NullPointerException() 异常类(如 IOException, Exception
数量 一次只能抛出一个异常 可以声明多个异常(用逗号分隔)
是否必须处理 抛出后必须被捕获或继续向上抛 声明后由调用者决定如何处理

四.自定义异常

在 Java 中,自定义异常(Custom Exception) 是指开发者根据业务需求创建的异常类。Java 提供的内置异常(如 NullPointerExceptionIOException 等)虽然覆盖了很多场景,但在实际开发中,常常需要更具体、语义更清晰的异常来表达特定错误。

1.为什么要自定义异常

  • 提高代码可读性 :比如 InvalidOrderStatusExceptionIllegalArgumentException 更明确。
  • 便于分类处理 :不同业务异常可以被不同的 catch 块处理。
  • 封装业务逻辑错误:将业务规则校验失败以异常形式抛出。

2.如何自定义异常

自定义异常类必须继承自 Throwable 的子类,通常选择:

  • 继承 Exception检查异常(Checked Exception)
  • 继承 RuntimeException非检查异常(Unchecked Exception)

推荐 :大多数业务异常继承 RuntimeException,避免强制调用者处理,保持代码简洁。

示例1:自定义非检查异常(推荐)

java 复制代码
// 自定义运行时异常(非检查异常)
public class InsufficientBalanceException extends RuntimeException {
    public InsufficientBalanceException(String message) {
        super(message);
    }

    // 可选:带 cause 的构造器(用于异常链)
    public InsufficientBalanceException(String message, Throwable cause) {
        super(message, cause);
    }
}

用法:

java 复制代码
public class BankAccount {
    private double balance;

    public BankAccount(double balance) {
        this.balance = balance;
    }

    public void withdraw(double amount) {
        if (amount > balance) {
            throw new InsufficientBalanceException("余额不足!当前余额: " + balance);
        }
        balance -= amount;
        System.out.println("取款成功,剩余余额: " + balance);
    }
}

// 测试
public class Main {
    public static void main(String[] args) {
        BankAccount account = new BankAccount(100);
        try {
            account.withdraw(150);
        } catch (InsufficientBalanceException e) {
            System.err.println("业务错误: " + e.getMessage());
        }
    }
}
场景 推荐类型
业务规则校验失败(如参数非法、状态不对) RuntimeException(非检查)
外部资源问题(如网络、文件、数据库)且可恢复 Exception(检查异常)
内容 说明
继承哪个类? RuntimeException(推荐)或 Exception
关键构造器 至少提供 String message 构造器
何时用? 表达特定业务错误,提升代码清晰度
命名 Exception 结尾,语义明确
相关推荐
MoonPointer-Byte2 小时前
【Python实战】我开发了一款“诗意”待办软件:MoonTask(附源码+工程化思路)
开发语言·python·custom tkinter
~央千澈~2 小时前
抖音弹幕游戏开发之第11集:礼物触发功能·优雅草云桧·卓伊凡
java·前端·python
wuqingshun3141592 小时前
说一下HashMap和HashTable的区别
java·开发语言
沐知全栈开发2 小时前
Bootstrap 多媒体对象
开发语言
PythonFun2 小时前
WPS动态序号填充,告别手动调整烦恼
java·前端·python
Hx_Ma162 小时前
测试题(二)
java·开发语言
ShineWinsu2 小时前
对于C++中list的详细介绍
开发语言·数据结构·c++·算法·面试·stl·list
tackchen2 小时前
venv-manager 管理 Conda 环境 和 Python 虚拟环境 (venv)
开发语言·python·conda
lly2024062 小时前
ASP #include 指令详解
开发语言