一、什么是异常?
异常就是程序运行的时候,出现的意外情况,比如做除法时把除数设成0、想操作一个对象却发现它是空的、想打开一个文件却找不到,这些情况都会打断程序的正常运行,要是不管它,程序就会直接崩溃。
二、异常分两类,处理要求不一样
- 受检异常:Java强制要求必须处理的异常。要么用try-catch接住处理,要么用throws告诉调用者"我可能出这个问题,你自己处理",不处理的话,代码都编译不过,没法运行。
- 非受检异常:Java不强制处理的异常,比如除零异常、空指针异常。这些大多是写代码时的小失误,Java允许不专门处理,要么被上层代码处理,要么直接导致程序崩溃,相当于提醒你下次别犯这错。
三、try-catch-finally:处理异常的"三件套"
这是处理异常最基础的方式,三者分工明确,用法很简单,就像处理生活中的意外一样。
3.1 三者各自的作用
| 组成部分 | 大白话作用 | 执行规则 |
|---|---|---|
| try | 装可能出问题的代码(比如做除法、打开文件) | 没异常:从头到尾执行完;有异常:执行到出问题的地方就停,跳去看catch |
| catch | 接住并处理异常(比如提示"除数不能为0") | 只有try里的异常和catch声明的类型一样,才会执行;类型不匹配就不执行,程序后续会崩溃 |
| finally | 做收尾工作(比如关闭文件、清理资源) | 不管有没有异常、catch有没有接住,一定执行(除非强制关掉程序) |
3.2 三种执行场景(一看就懂)
- 场景1:try里没异常 → 跑完try,跳过catch,执行finally,继续跑后面的代码
- 场景2:try里出异常,且catch能接住 → 执行到异常点停,执行catch,再执行finally,继续跑后面的代码
- 场景3:try里出异常,catch接不住 → 执行到异常点停,跳过catch,执行finally,然后程序崩溃,后面的代码不跑
四、throw和throws:别搞混这两个"抛异常"的关键字
两者都和抛异常有关,但用法完全不一样,用表格一对比就清楚,不用死记硬背。
| 关键字 | 写在哪个位置 | 怎么写(简单示例) | 大白话作用 |
|---|---|---|---|
| throw | 方法里面 | throw new 异常对象(); | 主动制造一个具体的异常(比如发现除数是0,主动抛出"除数不能为0"),是异常的源头 |
| throws | 方法声明处(方法名后面) | 方法名(参数) throws 异常类型; | 告诉调用者:"我可能会出某种异常,你要么自己处理,要么继续告诉别人" |
4.1 必看注意点
- throw后面必须跟具体的异常对象(比如throw new ArithmeticException("除数不能为0")),不能只写异常类型;
- throws后面必须跟异常类型(可多个,用逗号隔开),不能跟异常对象;
- 如果用throw抛出受检异常(Java强制处理的),要么在方法里用try-catch处理,要么用throws声明,不然代码编译不过;
- 抛出非受检异常(比如除零异常),可以不用写throws,Java会自动往上传递。
五、完整可运行代码示例(大白话注释)
下面的代码包含了所有知识点,注释都是大白话,直接复制到Java编辑器就能运行,跑一遍就懂所有逻辑。
csharp
/**
* Java异常处理大白话演示
* 包含:try-catch-finally、throw、throws、两种异常处理
* 新手也能看懂的注释
*/
public class ExceptionDemo {
public static void main(String[] args) {
// 测试1:没出异常的情况
System.out.println("===== 测试1:没出异常 =====");
try {
divide(12, 4); // 除数不是0,没异常
System.out.println("try里的代码跑完了,没出问题");
} catch (ArithmeticException e) {
System.out.println("捕获到异常:" + e.getMessage()); // 不会执行
} finally {
System.out.println("finally执行:清理资源\n"); // 必执行
}
// 测试2:出异常但被catch接住
System.out.println("===== 测试2:出异常但被接住 =====");
try {
divide(12, 0); // 除数是0,出异常
System.out.println("try里的代码跑完了,没出问题"); // 不会执行
} catch (ArithmeticException e) {
System.out.println("捕获到异常:" + e.getMessage()); // 执行处理
} finally {
System.out.println("finally执行:清理资源\n"); // 必执行
}
// 测试3:出异常但catch接不住
System.out.println("===== 测试3:出异常但接不住 =====");
try {
divide(12, 0); // 出除零异常
} catch (NullPointerException e) {
System.out.println("捕获到空指针异常"); // 类型不匹配,不执行
} finally {
System.out.println("finally执行:清理资源"); // 必执行
}
System.out.println("测试3执行完毕"); // 程序崩溃,不会执行
// 测试4:受检异常处理
System.out.println("===== 测试4:受检异常处理 =====");
try {
checkException(); // 调用声明了受检异常的方法,必须用try-catch
} catch (Exception e) {
System.out.println("捕获到受检异常:" + e.getMessage());
throw new RuntimeException(e); // 可选:转成非受检异常,不用层层写throws
} finally {
System.out.println("finally执行:清理资源");
}
}
/**
* 除法方法,演示非受检异常的throw和throws
* @param num1 被除数
* @param num2 除数
* @throws ArithmeticException 告诉调用者:我可能出除零异常
*/
public static void divide(int num1, int num2) throws ArithmeticException {
if (num2 == 0) {
// 主动抛出除零异常,提示用户输错了
throw new ArithmeticException("除数不能为0,你输错啦!");
}
System.out.println("除法结果:" + num1 + " ÷ " + num2 + " = " + (num1 / num2));
}
/**
* 演示受检异常的throw和throws
* @throws Exception 告诉调用者:我可能出受检异常
*/
public static void checkException() throws Exception {
boolean hasError = true; // 模拟业务执行失败
if (hasError) {
// 主动抛出受检异常,说明业务没成功
throw new Exception("模拟受检异常:业务没执行成功");
}
System.out.println("业务执行成功"); // 不会执行
}
}
六、异常处理小技巧(大白话版)
- 尽量精准捕获异常:能捕获"除零异常",就别直接捕获所有异常(Exception),不然找不到具体问题在哪;
- 受检异常嫌麻烦可以转:把受检异常包装成非受检异常(throw new RuntimeException(e)),不用层层写throws;
- finally一定要用对:打开文件、连接数据库后,不管有没有异常,都要在finally里关掉,避免浪费资源;
- 分工要明确:底层方法(比如divide)只负责抛异常(throws),上层调用者(比如main)负责处理异常(try-catch),代码更清晰。
七、总结
异常处理的核心就是"不让程序崩溃",遇到意外要么接住处理,要么告诉别人处理。try-catch-finally是基础工具,throw是主动抛具体异常,throws是提醒可能出异常,分清两种异常的处理要求,跟着小技巧写,就能写出稳当、好维护的代码。