异常
1. 异常概念与体系结构
1.1 异常的概念
概念:程序执行过程中发生的不正常行为
常见异常示例:
// 1. 算术异常
System.out.println(10 / 0); // ArithmeticException: / by zero
// 2. 数组越界异常
int[] arr = {1, 2, 3};
System.out.println(arr[100]); // ArrayIndexOutOfBoundsException
// 3. 空指针异常
int[] arr2 = null;
System.out.println(arr2.length); // NullPointerException
注意:
-
编译错误(语法错误)不是异常
-
异常发生在运行时
1.2 异常体系结构
Throwable
├── Error(错误,JVM无法处理)
│ ├── StackOverflowError
│ └── OutOfMemoryError
│
└── Exception(异常,程序员可处理)
├── RuntimeException(运行时异常/非受查异常)
└── 其他Exception(编译时异常/受查异常)
注意:
-
Error:严重问题,程序无法恢复
-
Exception:可通过代码处理
1.3 异常的分类
1. 编译时异常(Checked Exception)
// CloneNotSupportedException是编译时异常
@Override
public Person clone() throws CloneNotSupportedException {
return (Person)super.clone(); // 必须处理异常
}
特点:
-
编译时检查
-
必须处理(try-catch或throws)
2. 运行时异常(RuntimeException/Unchecked Exception)
// 常见运行时异常
int[] arr = {1, 2, 3};
System.out.println(arr[3]); // 运行时才抛出异常
常见运行时异常:
-
NullPointerException
-
ArrayIndexOutOfBoundsException
-
ArithmeticException
-
ClassCastException
-
IllegalArgumentException
注意:RuntimeException及其子类都是运行时异常
2. 异常的处理
2.1 防御式编程
1. LBYL(事前防御)
boolean ret = login();
if (!ret) {
// 处理登录错误
return;
}
ret = startMatch();
if (!ret) {
// 处理匹配错误
return;
}
// ...更多检查
缺点:正常流程和错误处理混在一起
2. EAFP(事后处理)
try {
login();
startMatch();
// 正常流程
} catch (LoginException e) {
// 处理登录异常
} catch (MatchException e) {
// 处理匹配异常
}
优点:流程清晰,Java主要采用这种方式
2.2 抛出异常(throw)
概念:主动抛出异常对象
语法:
throw new XXXException("异常原因");
示例:
public static int getElement(int[] array, int index) {
if (array == null) {
throw new NullPointerException("数组为null");
}
if (index < 0 || index >= array.length) {
throw new ArrayIndexOutOfBoundsException("索引越界: " + index);
}
return array[index];
}
注意易错点:
-
throw必须在方法体内
-
只能抛出Throwable或其子类对象
-
抛出RuntimeException可不处理
-
抛出编译时异常必须处理
-
throw后代码不会执行
2.3 异常声明(throws)
概念:声明方法可能抛出的异常,让调用者处理
语法:
修饰符 返回值类型 方法名(参数) throws 异常类型1, 异常类型2...
示例:
public void readFile(String filename) throws FileNotFoundException, IOException {
if (!filename.endsWith(".txt")) {
throw new IOException("不是文本文件");
}
if (!new File(filename).exists()) {
throw new FileNotFoundException("文件不存在");
}
// 读取文件...
}
注意易错点:
-
跟在参数列表后
-
可声明多个异常,用逗号分隔
-
有父子关系时,声明父类即可
-
调用者必须处理或继续throws
快捷键:Alt + Enter 快速处理异常
2.4 捕获异常(try-catch)
概念:捕获并处理异常
语法:
try {
// 可能出错的代码
} catch (异常类型1 e) {
// 处理异常1
} catch (异常类型2 e) {
// 处理异常2
} finally {
// 总会执行的代码
}
示例:
try {
int[] arr = {1, 2, 3};
System.out.println(arr[3]);
} catch (ArrayIndexOutOfBoundsException e) {
// 处理方式1:打印信息
System.out.println("错误信息: " + e.getMessage());
// 处理方式2:简单输出
System.out.println(e);
// 处理方式3:完整堆栈跟踪
e.printStackTrace();
} finally {
System.out.println("finally块总执行");
}
System.out.println("异常处理后继续执行");
处理方式:
-
让程序崩溃(严重问题,如支付错误)
-
记录日志(一般问题,发报警给程序员)
-
重试操作(网络问题等可恢复问题)
注意易错点:
1. 异常类型不匹配
try {
int[] arr = {1, 2, 3};
System.out.println(arr[3]); // 抛出ArrayIndexOutOfBoundsException
} catch (NullPointerException e) { // 类型不匹配,捕获失败
e.printStackTrace();
}
// 异常继续向外抛出,程序终止
2. 多异常处理
// 方式1:多个catch块
try {
// 可能抛出多种异常
} catch (ArrayIndexOutOfBoundsException e) {
// 处理数组越界
} catch (NullPointerException e) {
// 处理空指针
} catch (Exception e) { // 父类放最后
// 处理其他异常
}
// 方式2:合并catch(JDK7+)
try {
// ...
} catch (ArrayIndexOutOfBoundsException | NullPointerException e) {
// 处理两种异常
}
// 错误:父类在前,子类永远执行不到
try {
// ...
} catch (Exception e) { // 父类
// 这里会捕获所有异常
} catch (NullPointerException e) { // 子类,编译错误!
// 永远执行不到
}
3. finally的特殊情况
public static int testFinally() {
try {
return 10;
} finally {
return 20; // finally的return会覆盖try的return
}
}
// 返回20,不是10!
finally面试题:
// 问题1:下面代码输出什么?
public static int test1() {
try {
return func();
} finally {
System.out.println("finally");
}
}
// 先执行finally,再返回func()的值
// 问题2:finally一定会执行吗?
// 答:几乎总是执行,除非:
// 1. System.exit(0)退出程序
// 2. 断电、系统崩溃
// 3. 守护线程被结束
3. 异常处理流程
完整流程:
-
执行try中代码
-
如果发生异常,跳转到匹配的catch
-
执行匹配的catch块
-
执行finally块
-
继续执行后续代码
调用栈传递:
public class Test {
public static void main(String[] args) {
try {
method1();
} catch (Exception e) {
e.printStackTrace();
}
}
static void method1() {
method2();
}
static void method2() {
int[] arr = {1, 2, 3};
System.out.println(arr[3]); // 异常从这里抛出
}
}
输出堆栈跟踪:
java.lang.ArrayIndexOutOfBoundsException: 3
at Test.method2(Test.java:18) ← 异常发生位置
at Test.method1(Test.java:13) ← 调用链
at Test.main(Test.java:7) ← 调用链
注意:异常会沿着调用栈向上传递,直到被捕获或到达JVM
4. 自定义异常
4.1 创建自定义异常
// 1. 继承Exception(编译时异常)
class MyCheckedException extends Exception {
public MyCheckedException(String message) {
super(message);
}
public MyCheckedException(String message, Throwable cause) {
super(message, cause);
}
}
// 2. 继承RuntimeException(运行时异常)
class MyRuntimeException extends RuntimeException {
public MyRuntimeException(String message) {
super(message);
}
}
4.2 使用示例:用户登录
// 1. 定义异常类
class UsernameException extends Exception {
public UsernameException(String message) {
super(message);
}
}
class PasswordException extends Exception {
public PasswordException(String message) {
super(message);
}
}
// 2. 业务类
class LoginService {
private String correctUsername = "admin";
private String correctPassword = "123456";
public void login(String username, String password)
throws UsernameException, PasswordException {
if (!correctUsername.equals(username)) {
throw new UsernameException("用户名错误: " + username);
}
if (!correctPassword.equals(password)) {
throw new PasswordException("密码错误");
}
System.out.println("登录成功!");
}
}
// 3. 使用
public class Test {
public static void main(String[] args) {
LoginService service = new LoginService();
try {
service.login("admin", "wrong");
} catch (UsernameException e) {
System.out.println("用户名异常: " + e.getMessage());
} catch (PasswordException e) {
System.out.println("密码异常: " + e.getMessage());
} finally {
System.out.println("登录流程结束");
}
}
}
最佳实践:
-
提供有意义的异常信息
-
可添加额外字段存储上下文信息
-
考虑异常链(cause参数)
-
覆盖toString()方便调试
5. 面试常见问题
1. throw vs throws
| 区别 | throw | throws |
|---|---|---|
| 位置 | 方法体内 | 方法声明处 |
| 数量 | 一次抛出一个异常 | 可声明多个异常 |
| 作用 | 主动抛出异常对象 | 声明可能抛出的异常类型 |
2. finally执行时机
public static int test() {
try {
System.out.println("try");
return 1; // 1. 计算返回值
} finally {
System.out.println("finally"); // 2. 执行finally
return 2; // 3. 覆盖返回值
}
// 返回2
}
3. 异常处理原则
-
具体明确:捕获具体异常,不要用Exception一把抓
-
及时处理:不要吞掉异常(空catch块)
-
记录日志:记录异常上下文信息
-
资源释放:在finally中释放资源
-
异常转换:底层异常转换为业务异常
4. 资源管理新方式(try-with-resources)
// JDK7+ 自动关闭资源
try (FileInputStream fis = new FileInputStream("test.txt");
BufferedReader br = new BufferedReader(new InputStreamReader(fis))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
// 自动调用close(),相当于在finally中关闭
关键总结
-
异常分类:
-
Error:JVM级,无法处理
-
Exception:可处理
-
RuntimeException:运行时异常
-
其他Exception:编译时异常
-
-
-
处理方式:
-
throw:抛出异常
-
throws:声明异常
-
try-catch-finally:捕获处理
-
-
自定义异常:
-
继承Exception或RuntimeException
-
提供构造方法
-
添加业务信息
-
-
最佳实践:
-
不要忽略异常
-
使用具体异常类型
-
清理资源用finally或try-with-resources
-
记录完整的异常信息
-