Java异常处理
·
一、异常概念
程序运行时,遇到"意料之外的情况"就会产生异常。比如:
- 用户输入了非法数据
- 要读取的文件不存在
- 网络突然断开
Java把这些意外情况封装成异常对象,程序员可以捕获并处理它们,而不是让程序直接崩溃。
异常的继承体系:
Throwable
├── Error → JVM级别错误,程序员无需处理(如内存溢出)
└── Exception → 程序级别异常,需要处理
├── RuntimeException → 运行时异常(非受检)
└── 其他Exception子类 → 受检异常,编译器强制处理
记住这个核心区别:
- 受检异常:编译时就必须处理,否则代码无法编译
- 非受检异常(RuntimeException):运行时才出现,编译器不强制处理
二、异常的抛出与捕捉
抛出异常
当程序遇到异常情况时,JVM会自动抛出一个异常对象。例如:
public class Demo {
public static void main(String[] args) {
int[] arr = new int[5];
arr[10] = 1; // 下标越界,JVM自动抛出 ArrayIndexOutOfBoundsException
}
}
运行结果:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 10 out of bounds for length 5
程序遇到异常后立即停止,后续代码不再执行。这就是为什么需要"捕捉"异常。
捕捉异常
使用 try-catch-finally 语法捕捉并处理异常:
try {
// 可能出现异常的代码
int result = 10 / 0;
} catch (ArithmeticException e) {
// 捕捉到异常后的处理逻辑
System.out.println("捕捉到异常:" + e.getMessage());
} finally {
// 无论是否发生异常,都会执行
System.out.println("finally块执行");
}
输出:
捕捉到异常:/ by zero
finally块执行
三个块的职责:
| 块 | 作用 |
|---|---|
try |
包裹可能出异常的代码 |
catch |
捕捉指定类型的异常并处理 |
finally |
收尾工作,如关闭文件、释放资源 |
捕捉多个异常时,子类异常写在前面:
try {
// ...
} catch (NullPointerException e) {
System.out.println("空指针:" + e.getMessage());
} catch (Exception e) {
// Exception是父类,放最后兜底
System.out.println("其他异常:" + e.getMessage());
}
三、Java常见的异常类
初学阶段最常遇到的异常:
| 异常类 | 触发原因 | 示例 |
|---|---|---|
NullPointerException |
对null对象调用方法 | String s = null; s.length(); |
ArrayIndexOutOfBoundsException |
数组下标越界 | arr[10](数组长度为5) |
ClassCastException |
类型强转失败 | (String) new Integer(1) |
NumberFormatException |
字符串转数字失败 | Integer.parseInt("abc") |
ArithmeticException |
算术错误 | 10 / 0 |
IOException |
文件/网络IO失败 | 读取不存在的文件 |
StackOverflowError |
方法无限递归 | 递归没有终止条件 |
NullPointerException是初学者最常遇到的异常,遇到时先检查哪个变量是null。
四、自定义异常
Java内置的异常类不够用时,可以自定义异常,让错误信息更贴近业务。
写法:继承 Exception 或 RuntimeException
// 自定义受检异常(继承Exception)
public class AgeException extends Exception {
public AgeException(String message) {
super(message);
}
}
// 使用自定义异常
public class Person {
private int age;
public void setAge(int age) throws AgeException {
if (age < 0 || age > 150) {
throw new AgeException("年龄不合法:" + age);
}
this.age = age;
}
}
// 调用
public static void main(String[] args) {
Person p = new Person();
try {
p.setAge(-5);
} catch (AgeException e) {
System.out.println("捕捉到自定义异常:" + e.getMessage());
}
}
输出:
捕捉到自定义异常:年龄不合法:-5
继承哪个?
- 继承
Exception:调用者必须处理(受检) - 继承
RuntimeException:调用者可以不处理(非受检,后端项目更常用)
五、在方法中抛出异常
使用 throws 关键字
throws 写在方法签名上,表示"这个方法可能抛出某种异常,调用者来处理":
// 声明方法可能抛出 IOException
public void readFile(String path) throws IOException {
FileReader fr = new FileReader(path);
}
// 调用者必须处理
public static void main(String[] args) {
try {
readFile("test.txt");
} catch (IOException e) {
System.out.println("文件读取失败:" + e.getMessage());
}
}
使用 throw 关键字
throw 写在方法体内,用于主动抛出一个异常对象:
public int divide(int a, int b) {
if (b == 0) {
throw new ArithmeticException("除数不能为0");
}
return a / b;
}
throw vs throws 对比:
throw |
throws |
|
|---|---|---|
| 位置 | 方法体内 | 方法签名 |
| 作用 | 抛出一个具体的异常对象 | 声明方法可能抛出的异常类型 |
| 数量 | 每次只抛一个 | 可声明多个,用逗号分隔 |
六、运行时异常
RuntimeException 及其子类统称运行时异常,特点是:
- 编译器不强制处理
- 通常由代码逻辑错误引起
- 可以不写
try-catch,但出现时程序会崩溃
// 这段代码编译通过,但运行时崩溃
public static void main(String[] args) {
String s = null;
System.out.println(s.length()); // NullPointerException
}
常见运行时异常及预防方式:
// NullPointerException → 使用前判空
if (s != null) {
System.out.println(s.length());
}
// ArrayIndexOutOfBoundsException → 访问前检查长度
if (index < arr.length) {
System.out.println(arr[index]);
}
// NumberFormatException → 转换前校验格式
try {
int num = Integer.parseInt(input);
} catch (NumberFormatException e) {
System.out.println("输入不是合法数字");
}
运行时异常虽然不强制处理,但在关键位置(如接收用户输入)仍应捕获,避免程序崩溃。
七、异常的使用原则
1. 不要用异常控制正常流程
// 错误:用异常做条件判断,性能差
try {
int val = Integer.parseInt(str);
} catch (NumberFormatException e) {
val = 0;
}
// 正确:先判断
if (str.matches("\\d+")) {
int val = Integer.parseInt(str);
}
2. 不要吞掉异常(空catch块)
// 错误:出了问题完全不知道
try {
doSomething();
} catch (Exception e) {
// 什么都不写
}
// 正确:至少打印异常信息
} catch (Exception e) {
e.printStackTrace();
}
3. 捕获具体的异常类型,而非一律 Exception
// 不推荐
catch (Exception e) { ... }
// 推荐
catch (IOException e) { ... }
catch (NumberFormatException e) { ... }
4. 异常信息要有意义
// 差
throw new Exception("错误");
// 好
throw new IllegalArgumentException("用户年龄不合法,输入值:" + age);
5. 资源必须关闭(推荐 try-with-resources)
try (FileReader fr = new FileReader("test.txt")) {
// 使用 fr
} catch (IOException e) {
e.printStackTrace();
}
// fr 自动关闭,无需 finally
八、实践练习
练习1:捕捉数组越界异常
public class Exercise1 {
public static void main(String[] args) {
int[] arr = {1, 2, 3};
try {
System.out.println(arr[4]);
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("下标越界:" + e.getMessage());
}
}
}
练习2:自定义异常
public class ScoreException extends RuntimeException {
public ScoreException(String message) {
super(message);
}
}
public class Student {
public void setScore(int score) {
if (score < 0 || score > 100) {
throw new ScoreException("分数不合法:" + score);
}
System.out.println("设置分数:" + score);
}
public static void main(String[] args) {
Student s = new Student();
try {
s.setScore(150);
} catch (ScoreException e) {
System.out.println(e.getMessage());
}
}
}
练习3:使用 throws 声明异常
public class Exercise3 {
public static String readFirstLine(String path) throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
return br.readLine();
}
}
public static void main(String[] args) {
try {
String line = readFirstLine("hello.txt");
System.out.println(line);
} catch (IOException e) {
System.out.println("文件读取失败:" + e.getMessage());
}
}
}
本章总结
| 知识点 | 核心要点 |
|---|---|
| 异常概念 | 程序运行时的意外情况,分受检/非受检两类 |
| try-catch-finally | 捕捉异常的基本语法,finally必定执行 |
| 常见异常类 | NPE、越界、类型转换、数字格式等 |
| 自定义异常 | 继承 Exception 或 RuntimeException |
| throws | 方法签名上声明,交给调用者处理 |
| throw | 方法体内主动抛出异常对象 |
| 运行时异常 | 不强制处理,但关键位置应捕获 |
| 使用原则 | 不吞异常、不用异常控流、捕获具体类型 |
更多推荐
所有评论(0)