日期:2026-07-28
项目:c260728
作者:WanJL
异常(Exception) 就是程序出现不正常的情况。
在 Java 中,异常和错误都继承自 Throwable 类:
Throwable(顶级父类)
├── Exception(异常类 — 程序本身可以处理的问题)
│ ├── RuntimeException(运行时异常)
│ │ ├── ArithmeticException(算术异常,如除零)
│ │ ├── ArrayIndexOutOfBoundsException(数组下标越界)
│ │ ├── NullPointerException(空指针异常)
│ │ └── ...
│ └── 非 RuntimeException(编译期异常 — 编译时必须处理)
│ ├── IOException
│ ├── SQLException
│ └── ...
└── Error(错误类 — 严重问题,代码无法处理)
├── StackOverflowError(栈溢出,如无限制递归)
├── OutOfMemoryError(内存溢出)
└── ...
📄 来源:
Demo01.java
核心要点:
Exception 及其子类可以被我们捕获处理或抛出,是程序可以处理的问题。Error 是内存级别的严重问题,不是我们可以捕获抛出的,需要改代码。Exception 类或其子类,才可以被我们捕获处理或抛出。| 类型 | 说明 | 示例 |
|---|---|---|
| 编译期异常(Checked Exception) | 编译时就会报错,必须处理(捕获/抛出)才能通过编译 | IOException, SQLException |
| 运行时异常(Runtime Exception) | 编译时不报错,运行后才报错。所有运行时异常的父类是 RuntimeException |
ArithmeticException, NullPointerException, ArrayIndexOutOfBoundsException |
Java 程序执行流程:
.java(源代码) ---[javac 编译]---> .class(字节码) ---[java 解释]---> 运行结果
编译期 运行期
📄 来源:
Demo01.java
如果程序出了问题,我们不做任何处理,最终会由 JVM 做默认处理,处理步骤:
📄 来源:
Demo01.java
try {
// 可能出现异常的代码
} catch (异常类型1 变量名) {
// 异常类型1的处理方式
} catch (异常类型2 变量名) {
// 异常类型2的处理方式
} catch (异常类型n 变量名) {
// 异常类型n的处理方式
}
catch 块遵循 从上到下匹配 的原则int a = 0;
int[] arr;
try {
arr = new int[]{};
arr[3] = 5; // 数组下标越界 → ArrayIndexOutOfBoundsException
System.out.println("55555555555555555"); // 不会执行
} catch (ArithmeticException e) {
System.out.println("不能除以0");
System.out.println("以及一些其他的异常处理操作.....");
System.out.println(e.getMessage()); // 获取异常描述信息
} catch (RuntimeException e) {
System.out.println("出现了运行时异常");
System.out.println("输出的异常信息:" + e.getMessage()); // 异常描述
System.out.println("输出完整的异常信息:" + e.fillInStackTrace()); // 完整堆栈追踪
System.out.println(e.getCause()); // 异常原因(若无返回null)
} catch (Exception e) {
System.out.println("出现了异常..但不是运行时异常");
System.out.println(e.getMessage());
}
System.out.println(a);
System.out.println("........后续的代码正在运行................");
📄 来源:
Demo01.java(更新后)
输出结果分析: 由于 arr[3] = 5 抛出的是 ArrayIndexOutOfBoundsException,它是 RuntimeException 的子类,因此会被第二个 catch 块捕获,输出"出现了运行时异常",然后程序继续执行后续代码。
当 catch 捕获到异常对象 e 后,可以调用 Throwable 类提供的方法来获取异常的详细信息:
| 方法 | 说明 |
|---|---|
String getMessage() |
返回异常的详细描述信息(最常用) |
Throwable fillInStackTrace() |
返回完整的堆栈追踪信息 |
Throwable getCause() |
返回异常的原因(若无 cause 则返回 null) |
void printStackTrace() |
将异常的堆栈追踪信息输出到标准错误流 |
catch (RuntimeException e) {
System.out.println(e.getMessage()); // 如:"Index 3 out of bounds for length 0"
System.out.println(e.fillInStackTrace()); // 完整堆栈信息
System.out.println(e.getCause()); // 异常原因(通常为 null)
}
📄 来源:
Demo01.java(catch 块中的新增调用)
我们不处理异常,交给调用者去处理。如果方法内包含编译期异常,可以不在方法内处理,只使用 throws 抛出:
最终抛给 JVM,JVM 进行默认的处理
public void method() throws IOException {
// 方法体中的编译期异常不处理,交给调用者
}
📄 来源:
Demo01.java(概念说明)
📄 来源:
Demo01.java
无论是自己处理异常(try-catch),还是抛出异常(throw),异常代码后面的代码一般都不会继续执行了。但有些代码是必须执行的,比如:
Scanner、文件流、数据库连接等)finally 块就可以保证这些代码无论是否发生异常,都一定会被执行。
| 组合 | 格式 | 说明 |
|---|---|---|
| 最完整 | try { ... } catch (E e) { ... } finally { ... } |
捕获异常 + 释放资源 |
| 基本 | try { ... } catch (E e) { ... } |
仅捕获处理异常 |
| 扫描但不处理 | try { ... } finally { ... } |
不捕获异常,但确保资源关闭(异常会继续向上抛) |
Scanner sc = new Scanner(System.in);
System.out.println("请输入:");
String s = sc.next();
System.out.println("您输出的是:" + s);
try {
int i = 10 / 0; // 会抛出 ArithmeticException
} catch (Exception e) {
System.out.println(e.getMessage()); // 捕获并处理
} finally {
// finally 块中的代码一定会被执行
sc.close(); // 关闭 Scanner 资源
System.out.println("关闭资源....");
}
📄 来源:
Demo04.java
关键特点:
finally 块中的代码无论 try 块是否发生异常,都一定会执行return 语句,finally 块也会在 return 之前执行finally 块通常用于资源清理,如关闭 Scanner、关闭文件流等当 Java 官方提供的异常类不能满足我们的业务需求时,可以自定义异常类。例如,在年龄校验场景中,需要一个 AgeOutOfBoundsException 来表示年龄越界的业务异常。
| 步骤 | 说明 |
|---|---|
| 1 | 定义(创建)异常类 |
| 2 | 编写继承关系:继承 Exception(编译期异常)或继承 RuntimeException(运行时异常) |
| 3 | 提供 无参构造方法 |
| 4 | 提供 有参构造方法(带 String message 参数) |
📄 来源:
Demo02.java
// 自定义异常类 — 继承 Exception(编译期异常)
public class AgeOutOfBoundsException extends Exception {
// 无参构造方法
public AgeOutOfBoundsException() {
}
// 有参构造方法 — 传入异常描述信息
public AgeOutOfBoundsException(String message) {
super(message);
}
// 带 cause 的构造方法
public AgeOutOfBoundsException(String message, Throwable cause) {
super(message, cause);
}
// 仅带 cause 的构造方法
public AgeOutOfBoundsException(Throwable cause) {
super(cause);
}
}
📄 来源:
AgeOutOfBoundsException.java
注意事项:
Exception → 该类为 编译期异常(Checked Exception),调用者必须处理(try-catch 或 throws)RuntimeException → 该类为 运行时异常(Unchecked Exception),编译器不强制处理throw 关键字用于在 方法体内部 手动抛出一个异常对象。
throw new 异常类名();
throw new 异常类名("异常描述信息");
当方法内部检测到某种业务条件不满足时,手动创建异常对象并抛出。
public class Person {
private String name;
private int age;
// setAge 方法 — 使用 throw 手动抛出异常
public void setAge(int age) throws AgeOutOfBoundsException {
if (age < 0 || age > 130) {
// 手动创建异常对象并抛出
throw new AgeOutOfBoundsException("年龄越界了,最小是1岁,最大是130岁");
}
this.age = age;
}
}
📄 来源:
Person.java
逻辑说明:
if (age < 0 || age > 130) 为 true,表示年龄不合法throw new AgeOutOfBoundsException(...) — 手动创建异常对象并抛出,程序跳转到异常处理流程throw 被执行,后续的 this.age = age; 将不会执行throws 关键字用在 方法声明后面,后面跟着异常类名,表示该方法可能会抛出某种异常,调用者需要对此做准备。
public void 方法名(参数列表) throws 异常类名1, 异常类名2 {
// 方法体
}
public class Demo03 {
public static void main(String[] args) throws AgeOutOfBoundsException {
Person p = new Person();
p.setName("张三");
p.setAge(200); // 因为 setAge 声明了 throws AgeOutOfBoundsException
// 所以编译期会提示需要处理(try-catch 或继续 throws)
System.out.println("姓名:" + p.getName());
System.out.println("年龄:" + p.getAge());
}
}
📄 来源:
Demo03.java
| 对比维度 | throw |
throws |
|---|---|---|
| 位置 | 方法体 内部 | 方法声明 后面 |
| 后面跟的 | 异常 对象(new XXXException()) |
异常 类名(XXXException) |
| 作用 | 手动 抛出 异常对象 | 声明 方法可能抛出的异常 |
| 数量 | 一次只能抛出一个异常对象 | 一次可以声明多个异常(用逗号分隔) |
| 使用方式 | throw new 异常类名(); |
throws 异常类名1, 异常类名2 |
一句话总结:
throw — 在方法内部 实际抛出 一个异常("干活的")throws — 在方法签名上 声明 有风险,提醒调用者去处理("贴告示的")📄 来源:
Demo03.java
【自定义异常类】 AgeOutOfBoundsException extends Exception
↑ 继承
【Person.setAge()】 throw new AgeOutOfBoundsException("...") ← 使用 throw 手动抛出
↑ 声明
public void setAge(int age) throws AgeOutOfBoundsException
↑ 调用
【Demo03.main()】 有 choices:
a) try-catch 处理
b) 继续 throws 给 JVM
↑ 最终
【JVM】 默认处理:打印异常信息 + 终止程序
处理选择:
setAge(20) 时,编译期会提示该方法可能有异常throws),最终抛给 JVM给定有前后空格和重复项的字符串数据 " 张三, 李四, 王五, 赵六, 张三, 钱七 ",需要:
| 方法 | 说明 |
|---|---|
String.split(String regex) |
按指定正则表达式分割字符串为字符串数组 |
String.trim() |
去除字符串前后的空白字符 |
Arrays.toString(Object[] a) |
将数组转换为字符串表示形式 |
String data = " 张三, 李四, 王五, 赵六, 张三, 钱七 ";
// 第一步:分割 + 去空格
String[] split = data.split(",");
for (int i = 0; i < split.length; i++) {
split[i] = split[i].trim();
}
// 第二步:统计不重复元素个数
int count = 0;
for (int i = 0; i < split.length; i++) {
boolean b = false;
for (int j = 0; j < i; j++) {
if (split[i].equals(split[j])) {
b = true;
break;
}
}
if (b == false) {
count++;
}
}
// 第三步:创建新数组,放入不重复元素
String[] newArr = new String[count];
int index = 0;
for (int i = 0; i < split.length; i++) {
boolean b = false;
for (int j = 0; j < i; j++) {
if (split[i].equals(split[j])) {
b = true;
break;
}
}
if (b == false) {
newArr[index] = split[i];
index++;
}
}
for (int i = 0; i < newArr.length; i++) {
int count02 = 0;
for (int j = 0; j < split.length; j++) {
if (newArr[i].equals(split[j])) {
count02++;
}
}
System.out.println(newArr[i] + ":" + count02 + "次");
}
📄 来源:
StringUtilTest.java
关键知识点:
split[i].equals(split[j]) — 字符串比较使用 equals(),不能使用 ==boolean b)实现去重计算当前日期距离 2026-12-31 还有多少天。
| 方法 | 说明 |
|---|---|
LocalDate.of(int year, int month, int dayOfMonth) |
创建指定日期的 LocalDate 对象 |
LocalDate.toEpochDay() |
返回从 1970-01-01 到当前日期的天数 |
LocalDate localDate = LocalDate.of(2026, 7, 27);
LocalDate localDate2 = LocalDate.of(2026, 12, 31);
long now = localDate.toEpochDay(); // 距离 1970-01-01 有多少天
long epochDay = localDate2.toEpochDay(); // 距离 1970-01-01 有多少天
System.out.println("距离 2026-12-31 还有:" + (epochDay - now) + "天");
📄 来源:
DataMathTest.java
输出结果: 距离 2026-12-31 还有:157 天
关键知识点:
toEpochDay() 返回的是 long 类型,表示从 1970-01-01(Epoch)到当前日期的天数LocalDate.until(ChronoUnit.DAYS) 或 Period.between() 实现Throwable → Exception → RuntimeException 的继承体系try-catch 多 catch 块的写法,注意异常类型顺序(子类→父类)finally 块的使用:try-catch-finally(完整)、try-finally(扫描但不处理)finally 块一定会被执行的特性,常用于关闭资源(如 Scanner.close())JVM 默认处理异常的方式(打印信息 + 终止程序)getMessage()、fillInStackTrace()、getCause()、printStackTrace()throw 手动抛出异常对象(在方法体内部使用)throws 声明异常(在方法签名上使用),理解两者的区别split(",") 分割带分隔符的字符串trim() 去除字符串前后空格equals() 在字符串比较中的必要性LocalDate.of() 创建指定日期toEpochDay() 计算两个日期之间的天数差until()、Period.between()、ChronoUnit.DAYS.between()try(Resource r = new Resource()){...}),是 finally 关闭资源方式的进化版,无需手动写 finally 块initCause() 或构造方法将底层异常包装为高层异常Objects.requireNonNull() — 一种优雅避免 NullPointerException 的方式(JDK7+)Period 与 Duration — Java 8 日期时间 API 中更灵活的时间间隔计算LinkedHashSet 去重 — 利用集合类简化去重操作(替代手动数组去重)