欢迎访问作者Github:Joeysoda/Github_java: Study java
在 Java 中,异常是程序在执行过程中发生的非正常行为。理解并正确处理异常是编写健壮、可靠代码的关键。下面我们将详细讲解 Java 的异常处理机制,涵盖异常体系结构、异常的分类、异常的处理流程、以及关键字 try
、catch
、throw
、throws
、finally
的使用。
目录
面试题:finally 和 try-catch-finally 后的代码都会执行,为什么还要有 finally?
1. 异常的体系结构
1.1 Throwable
类
Java 中所有异常类都继承自 java.lang.Throwable
,它是 Java 中异常体系的根类。Throwable
有两个直接子类:
Error
:表示系统级错误,是 JVM 无法处理的严重问题,例如StackOverflowError
、OutOfMemoryError
。这些错误通常由 JVM 抛出,程序无法恢复或处理这些错误。Exception
:表示程序运行过程中可以预期并可以通过代码处理的异常,例如IOException
、NullPointerException
等。
1.2 Error
类
Error
类代表了 JVM 运行过程中出现的严重错误,程序员通常不需要或无法处理这些错误,属于系统级错误。- 典型的
Error
:StackOverflowError
:栈溢出错误。OutOfMemoryError
:内存溢出错误。
1.3 Exception
类
Exception
类是应用程序可以通过代码进行捕获和处理的异常类。典型的异常包括:ArithmeticException
:算术异常(如除以 0)。ArrayIndexOutOfBoundsException
:数组越界异常。NullPointerException
:空指针异常。
1.4 异常的分类
-
编译时异常(Checked Exception):
- 编译时异常是在代码编译阶段需要强制处理的异常。如果程序员没有正确处理这些异常,编译器会报错,无法通过编译。常见的编译时异常有
IOException
、SQLException
。 - 处理方式:需要通过
try-catch
或throws
来处理。
- 编译时异常是在代码编译阶段需要强制处理的异常。如果程序员没有正确处理这些异常,编译器会报错,无法通过编译。常见的编译时异常有
-
运行时异常(Unchecked Exception/RuntimeException):
- 运行时异常是在程序运行时发生的异常,通常是由于编程错误引起的。这类异常不要求在编译时必须处理,程序可以选择不捕获此类异常,直接由 JVM 处理。常见的运行时异常包括
NullPointerException
、ArrayIndexOutOfBoundsException
。 - 处理方式:可以不用强制处理,但建议通过良好的编程习惯来避免这些错误。
- 运行时异常是在程序运行时发生的异常,通常是由于编程错误引起的。这类异常不要求在编译时必须处理,程序可以选择不捕获此类异常,直接由 JVM 处理。常见的运行时异常包括
2. 异常的处理
2.1 防御性编程策略
-
LBYL(Look Before You Leap):事前检查。在执行操作之前,检查是否可能发生错误。这是一种预防性编程方法。
- 示例:
if (list != null && list.size() > 0) { System.out.println(list.get(0)); }
EAFP(Easier to Ask for Forgiveness than Permission):事后处理。这是一种容错性编程方法,不做过多检查,直接操作,若发生异常再进行处理。
- 示例:
try { System.out.println(list.get(0)); } catch (NullPointerException e) { System.out.println("列表为空!"); }
2.2 异常的抛出
在 Java 中,可以使用 throw
关键字手动抛出异常。常见的异常抛出场景包括参数校验失败等。
注意事项:
throw
必须写在方法体内。- 抛出的对象必须是
Throwable
的子类(Exception
或Error
)。 - 如果抛出的是
RuntimeException
或其子类,则可以不强制捕获或声明,由 JVM 处理。 - 如果抛出的是编译时异常(
Checked Exception
),调用者必须处理,否则无法通过编译。
示例:
public void validateAge(int age) {
if (age < 18) {
throw new IllegalArgumentException("年龄必须大于18");
}
}
2.3 异常的捕获
Java 提供了 try-catch
机制来捕获和处理异常。程序可以在 try
块中编写可能会抛出异常的代码,如果出现异常,就会跳到 catch
块中处理。
2.3.1 throws
声明
当一个方法内部可能抛出异常但不处理时,可以通过 throws
关键字将异常抛给上层调用者。
注意事项:
throws
必须跟在方法参数列表之后。throws
后的异常必须是Exception
或其子类。- 如果方法可能抛出多个异常,必须列出多个异常类型,或声明父类即可。
示例:
public void readFile(String path) throws IOException {
FileReader fileReader = new FileReader(path);
}
2.3.2 try-catch
捕获
try
块:包含可能发生异常的代码。catch
块:捕获异常并处理。
示例:
try {
int result = 10 / 0;
} catch (ArithmeticException e) {
System.out.println("除数不能为0");
}
2.3.3 finally
块
finally
块中的代码无论是否发生异常,都会执行,常用于资源清理操作。- 典型场景:关闭文件、释放数据库连接、关闭网络连接等。
示例:
try {
FileReader file = new FileReader("test.txt");
} catch (FileNotFoundException e) {
System.out.println("文件未找到");
} finally {
System.out.println("最终的资源清理操作");
}
面试题:finally
和 try-catch-finally
后的代码都会执行,为什么还要有 finally
?
finally
块的意义在于:无论try
中是否抛出异常,finally
块的代码一定会执行。即使在try
或catch
块中发生return
,finally
仍会被执行。try-catch-finally
后的代码不一定能执行到,因为如果try
或catch
中有return
或异常终止
,后续代码就不会执行,但finally
会被执行。
3. 面试题解析
3.1 throw
和 throws
的区别
-
throw
:用于显式抛出异常。它是用于在方法内部手动抛出异常的。
throw new IllegalArgumentException("无效参数");
-
throws
:用于声明异常,表示当前方法可能抛出异常,需要调用者处理。它用于方法声明中,放在参数列表后面。
3.2 finally
中的语句一定会执行吗?
- 是的,
finally
中的代码通常会执行,无论是否抛出了异常。但在某些极端情况下,finally
中的代码可能不会执行,例如:- 程序在
try
或catch
中调用了System.exit()
退出 JVM。 - JVM 崩溃,或系统中断。
- 程序所在的线程被强制终止。
- 程序在
4. 自定义异常类
在 Java 中,程序员可以自定义异常类,通常用于封装业务逻辑中的异常信息。自定义异常类通常继承自 Exception
或 RuntimeException
。
自定义异常注意事项:
- 继承自
Exception
的异常是受检异常,调用者必须处理。 - 继承自
RuntimeException
的异常是非受检异常,调用者可以选择不处理,直接交给 JVM 处理。
示例:自定义异常类
class AgeException extends Exception {
public AgeException(String message) {
super(message);
}
}
public void setAge(int age) throws AgeException {
if (age < 18) {
throw new AgeException("年龄不能小于18");
}
}
总结:异常的处理流程
- 程序执行
try
块中的代码。 - 如果
try
中的代码出现异常,执行会停止,检查catch
块中的异常类型是否匹配。- 如果匹配,执行
catch
中的代码。 - 如果没有找到匹配的
catch
,异常会继续向上层调用者传播。
- 如果匹配,执行
- 无论是否匹配到异常类型,
finally
块中的代码都会执行。 - 如果异常没有被任何地方捕获,最终会传递到
main
方法,程序会异常终止。
通过正确的异常处理机制,程序可以更好地处理运行时的错误,保持稳定性和健壮性。