一.认识异常(Exception),故障(Fault)和错误(error)
java中,一切皆对象。异常和错误也是对象!
1.1、 异常(Exception)(Java 核心概念)
定义
程序运行期间预料之内的不正常情况 ,通常由程序逻辑、输入数据、外部依赖(如文件不存在、网络超时)等问题引发,可以被开发者捕获并处理,从而让程序继续执行而非直接崩溃。
Java 中Exception是Throwable的子类,分为受检异常(Checked Exception) 和非受检异常(Unchecked Exception/RuntimeException)。
-
受检异常 (编译期强制检查):必须显式捕获或声明抛出,如
IOException、SQLExceptionjava
运行
// 示例:文件读取的受检异常 import java.io.FileReader; public class ExceptionDemo { public static void main(String[] args) { try { FileReader fr=new FileReader("test.txt"); // 可能抛出FileNotFoundException } catch (Exception e) { e.printStackTrace(); // 捕获并处理,程序不会崩溃 } } } -
非受检异常 (运行期抛出):通常是代码逻辑错误,如
NullPointerException、ArrayIndexOutOfBoundsException,无需强制声明java
运行
// 空指针异常(非受检) public class RuntimeExDemo { public static void main(String[] args) { String s=null; System.out.println(s.length()); // 运行时抛出NPE } }
核心特征
- 发生在应用层,由代码或外部输入导致
- 可控、可恢复 :通过
try-catch-finally捕获处理 - 不影响 JVM 本身的运行,仅影响当前线程
1.2、 错误(Error)(Java 核心概念,系统级)
定义
程序运行期间预料之外的、严重的系统级问题 ,通常由 JVM 或底层系统资源耗尽引发,开发者几乎无法捕获并处理,一旦发生,JVM 通常会终止线程甚至整个程序。
Java 中Error是Throwable的子类,典型例子:OutOfMemoryError(内存溢出)、StackOverflowError(栈溢出)、NoClassDefFoundError(类定义找不到)。
java
运行
// 示例:栈溢出错误
public class ErrorDemo {
public static void recursion() {
recursion(); // 无限递归,耗尽栈空间
}
public static void main(String[] args) {
try {
recursion();
} catch (StackOverflowError e) { // 理论可捕获,但无法恢复
e.printStackTrace();
// 此时JVM线程已损坏,无法正常执行后续逻辑
}
}
}
核心特征
- 发生在JVM / 系统层,由底层资源或系统错误导致
- 不可控、不可恢复:即使捕获,也无法修复底层问题
- 通常导致当前线程终止,严重时会让 JVM 崩溃
1.3、 故障(Fault)(通用工程 / 运维概念,非 Java 语法)
定义
系统组件或硬件的永久性 / 临时性缺陷 ,是导致错误或异常的根本原因,多用于分布式系统、运维领域。例如:服务器宕机、网络链路中断、数据库主从同步失败、磁盘坏道等。
故障本身不是代码层面的概念,而是基础设施层面的问题,会间接引发程序的异常或错误。
- 示例 1:数据库服务器故障(宕机)→ 程序抛出
SQLException(异常) - 示例 2:磁盘故障(坏道)→ JVM 读取类文件失败,抛出
NoClassDefFoundError(错误)
核心特征
- 发生在基础设施 / 硬件 / 第三方服务层
- 通常是外部问题,程序本身无法直接修复
- 影响范围大,可能导致多个服务 / 线程异常
1.4、 核心对比表格
| 维度 | 异常(Exception) | 错误(Error) | 故障(Fault) |
|---|---|---|---|
| 归属 | Java 语法,应用层 | Java 语法,JVM / 系统层 | 工程概念,基础设施层 |
| 引发原因 | 逻辑错误、输入异常、外部依赖异常 | JVM 资源耗尽、系统错误 | 硬件损坏、服务宕机、网络中断 |
| 可控性 | 可捕获、可处理、可恢复 | 几乎不可控、不可恢复 | 需运维干预修复(如重启服务、更换硬件) |
| 影响范围 | 单个线程 / 方法 | 单个线程 / JVM | 多个服务 / 整个系统 |
| 典型例子 | IOException、NullPointerException | OutOfMemoryError、StackOverflowError | 服务器宕机、磁盘坏道、网络分区 |
1.5、 补充说明与区分技巧
-
异常 vs 错误(Java 中):看
Throwable的子类,Exception是应用级问题,Error是 JVM 级问题java
运行
// Java中Throwable的继承结构 Throwable ├─ Exception(受检/非受检) └─ Error(系统级错误) -
故障 vs 异常 / 错误:故障是根因 ,异常 / 错误是故障的结果 。例如:网络故障 → 程序抛出
SocketException(异常) -
处理建议
- 异常:必须用
try-catch处理受检异常,非受检异常通过代码规范避免(如判空、边界检查) - 错误:无需主动捕获,通过监控告警(如内存监控、栈监控)提前预防
- 故障:通过熔断、降级、重试等架构设计(如 Sentinel、Resilience4j)降低影响
- 异常:必须用
二.Exception
2.1分类
编译时异常:
又叫受检异常。这个系列的异常,只要可能发生,编译器都会报错,提示你必须做出处理的操作。否则编译不通过,比如:IOException
运行时异常:
又叫非受检异常。这个系列的异常,无论是不是真的会发生,编译器都会视而不见,不会给出任何提示,只有在程序运行时才会暴露问题。例如:ArrayIndexOutOfBoundsException,NullPointException(用null对象来.方法),ClassCastException(类型转换异常),ArithmeticException(算术异常),InputMismatchException(输入不匹配异常)

三.异常处理
3.1语法格式

3.2try-catch结构

3.3求两数之商的异常处理
import org.junit.jupiter.api.Test;
import java.util.Scanner;
public class TestDivide {
/*
nextInt报错后,不会读入这个数据对吧,然后这个数据放在栈的缓冲区,需要我们nextLine读取掉但是不接受对吗?然后下次循
环就把它清出缓冲区对吗
* 你的理解与实际情况对照
你的理解: nextInt 报错后,不会读入这个数据对吧?
实际情况: 完全正确 。
当 nextInt() 发现输入流里的下一个标记(token)不是整数时,它会直接抛出异常并停止工作,它不会把这个错误的数据取走。
你的理解: 然后这个数据放在栈的缓冲区...
实际情况: 这个数据是放在 Scanner 的输入缓冲区(Buffer) 中,或者更准确地说,是留在 System.in 的流中。
纠正点:这里通常不叫“栈的缓冲区”。在计算机中,“栈”通常指方法调用栈。这里的存储区域更准确的叫法是 缓冲区(Buffer) 或 输入流(InputStream)27。
你的理解: 需要我们 nextLine 读取掉但是不接受对吗?
实际情况: 完全正确 。
我们调用 sc.nextLine() 的目的就是为了消耗(consume)掉这个残留在缓冲区里的错误数据。我们不需要把这个读取的值赋给任何变量(或者说,即使赋值了我们也不处理),目的仅仅是把它从缓冲区里“清空”13。
你的理解: 然后下次循环就把它清出缓冲区对吗?
实际情况: 逻辑正确。
执行完 nextLine() 后,缓冲区就被清空了。当 catch 块执行完毕,while 循环开始下一次迭代时,nextInt() 就会发现缓冲区是空的,从而停下来等待用户输入新的数据,而不是卡在旧的错误数据上26。
* */
@Test
public void testDivide() {
int a ,b;
Scanner sc = new Scanner(System.in);
while (true) {
try {
System.out.println("请输入第一个个数字a:");
a = sc.nextInt();
break;
} catch (Exception e) {
e.printStackTrace();//打印异常的详细信息,包装异常的堆栈跟踪信息,类型,异常的message等。
sc.nextLine();//这句代码是把当前输入的数据整行全部读取掉,但不接收,只是把数据丢弃。
}
}
while (true) {
try {
System.out.println("请输入第二个数字b:");
b = sc.nextInt();
if(b != 0)
break;
else
System.out.println("除数不能为0!");
} catch (Exception e) {
e.printStackTrace();
sc.nextLine();
}
}
double c = (double)a / b;
System.out.println("a / b = " + c);
sc.close();
}
}