作为一名Java开发人员,异常处理是一个无法回避的话题。无论你是初学者还是老手,精通异常处理对于写出高质量、可维护的代码至关重要。今天,我将与大家分享关于Java异常处理的一切,助你在代码质量的道路上突飞猛进!
一、什么是异常?
在我们深入探讨之前,先让我们理解一下什么是异常。异常(Exception)是指程序在执行过程中发生的不正常情况,如文件未找到、网络连接中断、数组越界等。如果不做任何处理,程序将终止运行。
二、Java异常体系结构
Java异常体系结构是Java语言中处理程序错误和异常情况的核心机制。异常是程序运行时发生的异常事件,它们可以由JVM自动抛出,也可以由程序显式抛出。Java异常体系结构基于类继承,所有的异常类都继承自Throwable
类。
Java异常体系结构的主要组成部分
-
Throwable类
- 所有异常或错误的超类。
- 有两个主要的子类:
Error
和Exception
。 - Throwable 包含了其线程创建时线程执行堆栈的快照,它提供了 printStackTrace() 等接口用于获取堆栈跟踪数据等信息。
-
Error类
-
Error 类及其子类:程序中无法处理的错误,表示编译时和系统错误(如
OutOfMemoryError
、StackOverflowError
),通常是不可查的(即程序无法预见和恢复的)。 -
这些错误通常表示JVM运行时出现了严重问题,此类错误发生时,JVM 将终止线程。
-
这些错误是不受检异常,非代码性错误。因此,当此类错误发生时,应用程序不应该去处理此类错误。按照Java惯例,我们不应该实现任何新的Error子类!
-
-
Exception类
- 表示程序可以捕捉和处理的异常情况。
- 有两个主要的子类:运行时异常(
RuntimeException
)和非运行时异常(checked exceptions)。
-
运行时异常(RuntimeException类)
- RuntimeException类及其子类异常,表示由于程序中的错误而导致的异常,比如
NullPointerException
、IndexOutOfBoundsException
等。 - 这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。但通常是由编程错误导致的,应该从逻辑角度尽可能避免这类异常的发生。
- 运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。
- RuntimeException类及其子类异常,表示由于程序中的错误而导致的异常,比如
-
非运行时异常(Checked Exceptions)
-
是RuntimeException以外的异常,类型上都属于Exception类及其子类,表示程序运行中可能出现的异常情况,但这些异常是可查的(checked)。
-
从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过,例如IOException
、
SQLException`等。
-
三、Java异常处理机制
1、异常关键字
-
try块
-
用于监听,将要被监听的代码(可能抛出异常的代码)放在try语句块之内,当try语句块内发生异常时,异常就被抛出。
-
可以有多个catch块来捕获不同类型的异常。
-
-
catch块
- 捕获并处理try块中抛出的异常。
- 可以有多个catch块来处理不同类型的异常。
-
finally块
- 无论是否发生异常,都会执行的代码块。
- 通常用于释放资源,如关闭文件流、数据库连接等。
- 只有finally块,执行完成之后,才会回来执行try或者catch块中的return或者throw语句,如果finally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止。
-
throw关键字
- 显式抛出一个异常。
-
throws关键字
- 用在方法签名中,用于声明该方法可能抛出的异常。
2、try-catch-finally 执行的顺序
-
当try没有捕获到异常时:try语句块中的语句逐一被执行,程序将跳过catch语句块,执行finally语句块和其后的语句。
-
当try捕获到异常,catch语句块里没有处理此异常的情况:
- 当try语句块里的某条语句出现异常时,而没有处理此异常的catch语句块时,此异常将会抛给JVM处理,finally语句块里的语句还是会被执行,但finally语句块后的语句不会被执行。
-
当try捕获到异常,catch语句块里有处理此异常的情况:
- 在try语句块中是按照顺序来执行的,当执行到某一条语句出现异常时,程序将跳到catch语句块,并与catch语句块逐一匹配,找到与之对应的处理程序,其他的catch语句块将不会被执行,而try语句块中,出现异常之后的语句也不会被执行,catch语句块执行完后,执行finally语句块里的语句,最后执行finally语句块后的语句。
示例代码:
public class ExceptionDemo {
public static void main(String[] args) {
try {
// 尝试执行可能抛出异常的代码
methodThatMayThrowException();
} catch (IOException e) {
// 处理IOException
e.printStackTrace();
} catch (Exception e) {
// 处理其他类型的异常
e.printStackTrace();
} finally {
// 无论是否发生异常,都会执行的代码
System.out.println("Finally block executed.");
}
}
public static void methodThatMayThrowException() throws IOException {
// 抛出一个IOException
throw new IOException("An I/O error occurred.");
}
}
在这个示例中,methodThatMayThrowException
方法声明可能会抛出IOException
,因此在main
方法中,我们需要用try-catch
块来捕获并处理这个异常。finally
块确保无论是否发生异常,都会执行一些清理工作。
通过这种异常体系结构,Java提供了一种强大的方式来处理程序中的错误和异常情况,使得程序更加健壮和易于维护。
3、异常的申明(throws)
在Java编程语言中,throws
关键字用于在方法声明中指定该方法可能会抛出的异常类型。当一个方法可能会抛出某种类型的异常,并且调用者需要处理这些异常时,方法声明中会使用throws
关键字来告知调用者。
(1)、何时使用throws
-
方法内部无法处理异常:如果方法内部的代码可能会抛出异常,并且该异常无法在方法内部被处理(例如,通过
try-catch
块),则需要使用throws
关键字来声明。 -
强制调用者处理异常:使用
throws
关键字可以强制调用者必须处理这些异常,要么通过捕获它们,要么通过进一步向上抛出。
(2)、语法
throws
关键字的基本语法如下:
public returnType methodName(parameterList) throws ExceptionType1, ExceptionType2, ... {
// 方法体
}
Throws抛出异常的规则:
- 如果是不可查异常(unchecked exception),即Error、RuntimeException或它们的子类,那么可以不使用throws关键字来声明要抛出的异常,编译仍能顺利通过,但在运行时会被系统抛出。
- 必须声明方法可抛出的任何可查异常(checked exception)。即如果一个方法可能出现受可查异常,要么用try-catch语句捕获,要么用throws子句声明将它抛出,否则会导致编译错误。
- 仅当抛出了异常,该方法的调用者才必须处理或者重新抛出该异常。当方法的调用者无力处理该异常的时候,应该继续抛出,而不是囫囵吞枣。
- 调用方法必须遵循任何可查异常的处理和声明规则。若覆盖一个方法,则不能声明与覆盖方法不同的异常。声明的任何异常必须是被覆盖方法所声明异常的同类或子类。
(3)、案例
假设有一个方法divide
,它可能会因为除数为0而抛出ArithmeticException
:
public class Calculator {
public int divide(int numerator, int denominator) throws ArithmeticException {
if (denominator == 0) {
throw new ArithmeticException("Cannot divide by zero");
}
return numerator / denominator;
}
}
在这个例子中,divide
方法声明了它可能会抛出ArithmeticException
。这意味着任何调用divide
方法的代码都必须处理这个异常,要么通过捕获它,要么通过在调用方法的声明中使用throws
关键字。
(4)、注意事项
- 异常链:当在
catch
块中抛出新的异常时,可以通过throw new ExceptionType(e)
将原始异常作为新异常的cause来传递,这样可以保留原始异常的信息。 - 检查型异常 vs. 非检查型异常:Java中的异常分为两类,检查型异常(checked exceptions)和非检查型异常(unchecked exceptions)。检查型异常是那些在编译时需要被处理的异常,而非检查型异常(如
RuntimeException
及其子类)则不需要。 - 避免过度使用:过度使用
throws
关键字可能会导致代码难以阅读和维护。如果可能,尽量在方法内部处理异常,而不是将其抛出。 - 文档化异常:在方法的文档注释中,应该详细说明哪些异常会被抛出,以及在什么情况下会抛出这些异常。
通过使用throws
关键字,Java程序