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 结尾,语义明确
相关推荐
小bo波12 小时前
使用Thread子类创建线程 VS 使用Runnable接口创建线程的区别
java·多线程·thread·并发编程·runnable
SamDeepThinking13 小时前
高并发场景下,CompletableFuture与ForkJoinPool该如何取舍?
java·后端·面试
张不才16 小时前
CPU 100% 了怎么办?Java 性能排障的标准化操作
java·后端
shepherd11117 小时前
吞吐量提升 10 倍:高并发大批量数据处理任务的架构演进与性能调优
java·后端·架构
plainGeekDev20 小时前
单例模式 → object 声明
android·java·kotlin
用户2986985301421 小时前
Java 实现 Word 文档文本与图片提取的方法
java·后端
SimonKing1 天前
铁子,IntelliJ IDEA 2026.1.3来了,升不升?
java·后端·程序员
咖啡八杯1 天前
GoF设计模式——策略模式
java·后端·spring·设计模式
用户128526116022 天前
我把祖传Java项目重构后,接口响应从3s砍到了200ms,只改了这几行代码
java