- 第 157 篇 -
Date: 2026 - 02- 02
Author: 郑龙浩(仟墨)
懒散了一阵子,好久没学Java了,今天将之前学的内容重新复习了一遍,然后还有两篇1月份的笔记没有发布,现在发一下其中一篇
文章目录
- 【Java加强】异常
-
- [1 异常的介绍](#1 异常的介绍)
- [2 异常的处理](#2 异常的处理)
-
- [2.1 抛出异常 (**throws**) - 上报问题](#2.1 抛出异常 (throws) - 上报问题)
- [2.2 捕获异常(**try...catch**) - 就地解决](#2.2 捕获异常(try…catch) - 就地解决)
- [3 异常的作用 | 异常处理的案例](#3 异常的作用 | 异常处理的案例)
- [4 自定义异常](#4 自定义异常)
-
- [4.1 自定义异常的分类](#4.1 自定义异常的分类)
- [4.2 自定义异常的步骤](#4.2 自定义异常的步骤)
- [4.2 自定义异常的步骤](#4.2 自定义异常的步骤)
-
- [4.3 案例 - 运行时异常](#4.3 案例 - 运行时异常)
- [4.3 关于自定义异常的注意事项](#4.3 关于自定义异常的注意事项)
【Java加强】异常
本篇笔记创作时间: 2026-01-06
1 异常的介绍
程序运行过程中发生的不正常情况,会中断正常的程序执行流程。
- 程序在运行时遇到的错误或意外情况
- 比如:除以0、访问不存在的文件、数组越界、空指针等
异常分类
-
Error: 表示严重的、程序无法处理的系统级错误,比如内存溢出(OutOfMemoryError)。这类错误通常由JVM抛出,应用程序一般无法处理,因此开发人员通常不关心也不捕获Error。
-
Exception: 表示程序运行时可能出现的、可以被捕获和处理的问题。程序员可可以处理的。
- 一种是运行时异常(RuntimeException),这种异常在写代码的时候,编译器不会提示错误,要等到运行程序时才会暴露出来。比如数组索引越界异常。
- 另一种是编译时异常(CheckedException),这种异常在写代码的时候,编译器就会直接提示错误,要求必须处理,否则程序无法运行。比如日期解析异常。
开发人员主要使用Exception及其子类来封装和处理程序中可能出现的问题。
Java报错的时候实际上是使用类报错的,而且比如抛出的NullPointerException报错,实际上就是一个类,且这个类就继承于其他类,这个其他类又继承于RuntimeException,也就是RuntimeExceptions实际上是个父类,它的下面有一堆的字类,用于报错提示。
-
错误是用"类"来表示的 :当发生空指针错误时,Java 实际上是在背后
new了一个NullPointerException类(子类)的对象,并把它"扔"了出来,我们才能在屏幕上看到这个错误。 -
异常之间有"父子"关系 :
NullPointerException这个类(子类)并不是独立的,它"继承"自另一个更通用的类,叫做RuntimeException。我可以把RuntimeException理解为运行时异常这个"大家族"的"总爸爸"。 -
不存在名为CheckedException的异常,这个只是个分类
RuntimeException, 和其他异常 都继承 Exception
CheckedException异常不存在,这个只是个分类Throwable(所有异常/错误的父类)
├── Error(错误,程序无法处理)
│ ├── OutOfMemoryError(内存溢出)
│ └── StackOverflowError(栈溢出)
│
└── Exception(异常,可以处理)
├── RuntimeException(运行时异常,可处理可不处理)
│ ├── NullPointerException(空指针异常)
│ ├── ArrayIndexOutOfBoundsException(数组越界)
│ └── ArithmeticException(算术异常,如除以0)
│
└── CheckedException(编译时异常,必须处理) -- 该异常不存在,仅是分类
├── IOException(IO异常)
├── FileNotFoundException(文件找不到)
└── SQLException(数据库异常)
2 异常的处理
2.1 抛出异常 (throws) - 上报问题
- 做法 :在定义方法时,使用
throws关键字声明 - 作用 :告诉调用这个方法的人:"我内部 可能会发生某种异常,但我不处理。如果真发生了,这个异常就扔给你去解决。"
Eg:这段代码的意思是: "我在方法内部不处理异常,谁调用我这个方法,谁就要负责处理可能出现的 IOException或 FileNotFoundException异常。"
将可能会出现的
IOException和FileNotFoundException两个异常抛出去了,谁调用谁处理调用这个方法的地方怎么解决:调用这个方法的地方,就可以写一个捕获异常,将这两个代码的异常处理掉
java
// 在方法上声明可能抛出的异常
public void readFile() throws IOException, FileNotFoundException {
// 方法内部可能抛出这些异常
// 调用者需要处理这些异常
if (...) {
throw new IOException("IOExceptiony异常,。。。有问题");
} else if (...){
throw new FileNotFoundException("FileNotFoundException异常,。。。有问题");
}
}
// 简单点,甚至可以改成抛 Exception,也就是只要遇到异常就抛出
public void readFile() throws Exception {
// 方法内部可能抛出异常(不管是什么异常,都抛出)
// 调用者需要处理这些异常
}
2.2 捕获异常(try...catch) - 就地解决
- 做法 :在方法内部,用
try{ }包裹可能出错的代码,用catch{ }来专门处理特定异常。 - 作用 :在出问题的地方立刻拦截并处理异常,防止程序直接崩溃,并给出备用方案。
java
try {
// 监视可能出现异常的代码!
int result = 10 / 0; // 这里会抛出ArithmeticException
} catch (ArithmeticException e) {
// 处理算术异常
System.out.println("不能除以零");
} catch (Exception e) {
// 处理其他异常
System.out.println("发生未知错误");
}
3 异常的作用 | 异常处理的案例
- 定位程序bug的关键信息
- 知上层调用者, 方法执行可能会出现的问题/异常
java
package zhenglonghao.exception1;
public class Exception1 {
public static void main(String[] args) {
// 目标:搞清楚异常的作用
System.out.println("程序开始执行...");
try {
// 注意:Java方法调用不支持参数命名,应改为 div(10, 0)
System.out.println(div(10, 0));
System.out.println("底层方法执行成功了");
} catch (Exception e) {
e.printStackTrace(); // 作用1:提供完整的错误定位信息
System.out.println("底层方法执行失败了");
}
System.out.println("程序结束执行...");
}
// 需求:求2个数的除的结果,并返回这个结果
public static int div(int a, int b) throws Exception { // 注意throws加了s
if (b == 0) {
// 作用2:通过抛出异常告知上层调用者,方法执行出现异常情况
// 异常可以携带错误信息,中断当前方法执行,并向上传递
throw new Exception("除数不能为0,您的参数有问题!"); // 注意throw没加s
}
int result = a / b;
return result;
}
}
/* 打印结果:(会有异常)
E:\dev\jdk25\bin\java.exe -javaagent:E:\SoftWare\IntelliJ_IDEA_2025.2.4\lib\idea_rt.jar=50429 -Dfile.encoding=UTF-8 -Dsun.stdout.encoding=UTF-8 -Dsun.stderr.encoding=UTF-8 -classpath E:\learning\Java开发\code\javaseaiproject\out\production\day14-exception-2025-01-06 zhenglonghao.exception1.Exception1
程序开始执行...
底层方法执行失败了
程序结束执行...
java.lang.Exception: 除数不能为0,您的参数有问题!
at zhenglonghao.exception1.Exception1.div(Exception1.java:26)
at zhenglonghao.exception1.Exception1.main(Exception1.java:11)
Process finished with exit code 0
*/
4 自定义异常
4.1 自定义异常的分类
在Java中,当企业有特定的问题需要以异常形式进行管理,而Java标准库未提供对应的异常类时,就需要自定义异常。自定义异常主要分为以下两类:
-
自定义运行时异常
-
定义 :继承自
java.lang.RuntimeException类。(实际上,RuntimeException也继承Exception类) -
特点:编译阶段不会强制检查和处理,代码能正常编译。异常在运行时可能出现,提醒方式相对不激进。
-
创建与抛出 :重写构造器,在需要时通过
throw new 异常类名()创建并抛出。
-
-
自定义编译时异常
-
定义 :继承自
java.lang.Exception类。 -
特点:编译阶段就会检查,如果存在未被处理的此类异常,代码将无法通过编译,提醒方式相对激进。
-
创建与抛出 :重写构造器,在需要时通过
throw new 异常类名()创建并抛出。
核心对比:
| 特性 | 自定义运行时异常 | 自定义编译时异常 |
|---|---|---|
| 继承的父类 | RuntimeException |
Exception |
| 编译时检查 | 不检查 | 检查,不处理则编译报错 |
| 使用场景 | 提示不很严重、通常由程序逻辑错误导致的非强制性问题。 | 提示严重、必须被显式处理的强制性问题。 |
| 方法签名(是否在自定义类内部使用throw抛出异常) | 方法签名不需要 throws声明(无需在自定义运行时异常的类中抛出异常) |
方法签名必须 用throws声明(必须在自定义编译时异常的类中抛出异常) |
一定要注意:
- 自定义运行时异常(比如类名A),不需要在A中抛出异常(比如不需要写:
throw new AgeTooYoungException("年龄未满18岁,禁止访问!您当前的年龄是:" + age); - 自定义编译时异常(比如类名B),则必须在B中抛出异常,也就是需要写出
throw new .....
4.2 自定义异常的步骤
4.2 自定义异常的步骤
1. 继承父类
创建一个新类,并继承相应的异常父类。
- 若需定义编译时异常 ,则继承
Exception类。 - 若需定义运行时异常 ,则继承
RuntimeException类。
2. 重写构造器
在自定义异常类中,重写父类的构造器。通常至少需要提供一个可以传递错误信息的构造器(即带 String message参数的构造器),这是报告异常详情的关键。
3. 抛出异常
在程序代码中,当业务逻辑遇到特定错误或无效状态时,使用 throw关键字,实例化并抛出您的自定义异常对象,从而中断当前执行流并将问题上报。
总结而言 :定义一个继承自目标父类(Exception或 RuntimeException)的新类,为其重写构造器,随后即可AgeTooYoungException在需要的地方通过 throw来使用它。
4.3 案例 - 运行时异常
在一个模拟"网吧"的类中,使用方法判断年龄,年龄<18时抛出我们的自定义异常。
AgeTooYoungException.java 文件
该文件存储的是自定义的运行时异常会抛出的类
注意,刚开始理解错了的点:
如果是运行时异常,是不是不需要在自定义的运行时异常的类中返回throw...的,而是需要在外面直接使用throw抛出
java
package zhenglonghao.exception1;
/**
* Date: 2025-01-06 Author: 郑龙浩
* 这是个「自定义运行时异常」
* 自定义的运行时异常:年龄太小异常
* 1. 继承 RuntimeException 做爸爸。
* 2. 重写 RuntimeException 的构造器。
*/
public class AgeTooYoungException extends RuntimeException { // 关键:继承 RuntimeException
// 1. 无参构造器
public AgeTooYoungException() {
}
// 2. 带消息的构造器(最常用,推荐重写这个)
public AgeTooYoungException(String message) {
super(message); // 调用父类构造器,把异常原因信息传进去
}
}
CyberCafe.java 文件
运行测试
java
package zhenglonghao.exception1;
import java.util.Scanner;
// Date: 2025-01-06 Author: 郑龙浩
// 自定义 「运行时的异常」
// 在一个模拟"网吧"的类中,在一个模拟"网吧"的类中,使用方法判断年龄,年龄<18时抛出我们的自定义异常。
public class
CyberCafe {
public void enter(int age) {
if (age < 18) {
// 3. 哪里需要用这个异常,哪里就 throw
// 因为它是运行时异常,所以方法签名上无需用 throws 声明
throw new AgeTooYoungException("年龄未满18岁,禁止访问!您当前的年龄是:" + age);
}
System.out.println("欢迎光临,祝您玩得愉快!");
}
public static void main(String[] args) {
CyberCafe cafe = new CyberCafe();
System.out.println("网吧开始营业...");
System.out.println("请输入您的年龄:");
Scanner scanner = new Scanner(System.in);
int age = scanner.nextInt();
try {
cafe.enter(age); // 如果是未成年,会抛出 AgeTooYoungException
} catch (AgeTooYoungException e) {
System.out.println("捕获到异常:" + e.getMessage());
}
}
}
4.3 关于自定义异常的注意事项
在 Java 里,尽量别自己定义那种"必须处理"的异常(编译时异常)。
为啥呢?因为你一定义这种异常,别人用你的代码时就被强制要求要么 try-catch,要么也往上抛。结果就是:
- 代码里到处是
try-catch,核心逻辑都看不清楚了。 - 或者异常被一层层机械地往上抛,最后谁也没真正处理,很麻烦。
所以行业里的共识是:优先考虑用运行时异常。这样更灵活,调用方爱处理就处理,不处理也不会编译报错,代码干净很多。
简单总结:
除非这个错误必须 让调用方当场处理(比如"账户余额不足"),否则都别定义编译时异常。绝大多数错误(像"参数传错了"、"连接超时了"),用自定义的运行时异常来表示就对了。
尽量使用运行,而非编译