文章目录
- 异常体系
- 异常声明
- 捕获多个异常
- Java中的哪些异常,程序不用捕获处理?【重要】
- try with resource 异常处理流程
- foreach中遇到异常
- 面试题
- try和finally中都由return
异常体系
异常声明
- 如果声明的是Exception,那么必须要处理
- 如果声明的是RuntimeException,那么可以不被处理
捕获多个异常
在Java中,如果你希望捕获try
代码块中可能抛出的多种异常,有几种不同的方法可以实现:
- 多个catch块:为每种异常类型提供一个单独的
catch
块。每个catch
块处理一种特定的异常类型。
try {
// 可能会抛出多种异常的代码
} catch (ExceptionType1 e1) {
// 处理 ExceptionType1
} catch (ExceptionType2 e2) {
// 处理 ExceptionType2
}
// ... 可以根据需要添加更多的 catch 块
- 通用的catch块:使用一个通用的
catch
块来捕获所有未被捕获的异常。这通常不是一个好习惯,因为它隐藏了具体的错误类型,使得调试更加困难。
try {
// 可能会抛出多种异常的代码
} catch (Exception e) {
// 捕获所有Exception的子类,不推荐这样做,因为不清楚具体是哪种异常
}
- 多异常类型捕获:从Java 7开始,你可以在一个
catch
块中捕获多个异常类型,使用圆括号将异常类型包围起来。
try {
// 可能会抛出多种异常的代码
} catch (ExceptionType1 | ExceptionType2 | ExceptionType3 e) {
// 同时处理 ExceptionType1, ExceptionType2 和 ExceptionType3
}
- ** finally 块**:如果无论是否发生异常,都需要执行一些代码(如资源清理),可以使用
finally
块。finally
块总是在try
和catch
块之后执行。
try {
// 可能会抛出异常的代码
} catch (ExceptionType1 | ExceptionType2 | ExceptionType3 e) {
// 处理异常
} finally {
// 无论是否发生异常都会执行的代码
}
- 异常链:当捕获一个异常并且需要抛出一个新的异常时,可以使用
Throwable
类的addSuppressed()
方法或构造函数中的cause
参数来保留原始异常的信息,形成异常链。
try {
// 可能会抛出多种异常的代码
} catch (ExceptionType1 | ExceptionType2 e) {
throw new WrappedException("Error message", e);
}
在这个例子中,WrappedException
是一个新的异常类型,它封装了捕获的异常作为其原因(cause
)。
选择使用哪种方法取决于你的具体需求:
- 如果不同的异常需要不同的处理逻辑,使用多个
catch
块。 - 如果你想要记录或以不同方式响应不同类型的异常,可以考虑使用多异常类型捕获。
- 如果你想要执行一些清理工作,无论是否发生异常,都使用
finally
块。 - 如果你想要封装异常并抛出一个新的异常,同时保留原始异常信息,使用异常链。
通常,推荐的做法是尽可能具体地捕获和处理异常,避免使用过于宽泛的异常捕获,这样可以使你的代码更加健壮,并且便于调试。
Java中的哪些异常,程序不用捕获处理?【重要】
在Java中,异常分为两种主要类型:受检异常(Checked Exceptions)和非受检异常(Unchecked Exceptions)。
-
受检异常(Checked Exceptions):这些是继承自
java.lang.Exception
类但不是继承自java.lang.RuntimeException
的异常。受检异常必须在方法中被捕获或者声明抛出。编译器会强制要求你处理这些异常,因为它们被认为是可以被预见并处理的情况。例如,IOException
和SQLException
就是受检异常。 -
非受检异常(Unchecked Exceptions):这些是继承自
java.lang.RuntimeException
的异常。非受检异常不需要在方法中被捕获或声明抛出。它们通常表示编程错误
,如空指针异常(NullPointerException
)、数组越界异常(ArrayIndexOutOfBoundsException
)和非法参数异常(IllegalArgumentException
)等。
对于非受检异常,程序可以选择不捕获处理
,但这通常不推荐
,因为不处理这些异常可能导致程序崩溃。然而,有些情况下,你可能决定不捕获特定的非受检异常,原因可能包括:
-
不可恢复的错误:当异常指示了一种无法恢复或者无需恢复的错误时,例如,程序的配置错误或严重违反了预期的运行时假设,你可能选择不捕获它,让程序崩溃并记录错误日志。
-
性能考虑:捕获异常是有性能成本的,对于性能敏感的代码段,如果异常发生的概率非常低,你可能选择不捕获。
-
框架或库的要求:某些框架或库可能要求你不捕获异常,以便它们可以在更高级别处理异常。
-
简化代码:在某些情况下,异常处理可能会使代码变得复杂,如果异常发生的可能性非常低,且对程序的影响不大,开发者可能会选择不捕获。
尽管如此,通常建议至少对所有可能的受检异常提供处理逻辑,以避免程序因未处理的异常而意外终止。对于非受检异常,即使选择不捕获它们,也应该在设计和文档中明确这一点,并确保它们不会对程序的其他部分产生不良影响。此外,对于关键的应用部分,记录日志是一个好习惯,这样即使程序崩溃,也能够了解崩溃的原因。
try with resource 异常处理流程
- 这里其实就是要注意,哪几种情况会异常会被catch住
foreach中遇到异常
- foreach循环会自动跳过遍历空集合,如果对于有null值的集合,碰到null时需注意NPE
在Java中,使用foreach
(也称为增强型for循环)进行迭代时,不推荐进行元素的remove
或add
操作,原因如下:
-
迭代器失效:在
foreach
循环中,实际上是使用迭代器来遍历集合的。当你尝试在迭代过程中修改集合(添加或删除元素),会导致迭代器失效,即迭代器不再能够安全地指向集合中的下一个元素。 -
并发修改异常:尝试在迭代过程中修改集合可能会抛出
ConcurrentModificationException
。这是因为foreach
循环隐式地为集合创建了一个迭代器,并在每次迭代时使用迭代器的next()
和hasNext()
方法。如果在迭代过程中修改了集合,就会违反迭代器的预期状态,导致异常。 -
逻辑错误:即使在某些情况下,如使用
ArrayList
,你可能会侥幸逃脱没有遇到异常,但这样的操作通常会导致逻辑错误。因为你可能会跳过一些元素或者重复处理一些元素,导致程序行为与预期不符。 -
性能问题:即使某些集合类型(如
LinkedList
)支持在迭代过程中进行添加或删除操作,这样做也可能引起性能问题。因为每个remove
或add
操作都可能涉及到大量的元素移动和列表的重新链接,这在大型集合上尤其低效。
如果你需要在迭代过程中修改集合,应该使用集合的迭代器来安全地进行这些操作。例如,对于ArrayList
,可以先复制需要删除的元素到另一个集合,然后在迭代结束后删除它们:
List<String> list = ...; // 假设这是你的列表
List<String> toRemove = new ArrayList<>();
for (String item : list) {
if (需要删除的条件) {
toRemove.add(item);
}
}
list.removeAll(toRemove);
对于LinkedList
,可以直接使用其迭代器进行删除和添加操作:
List<String> list = new LinkedList<>(); // 使用LinkedList
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if (需要删除的条件) {
iterator.remove();
}
// 可以在这里添加新元素到list中,不会导致迭代器失效
}
总之,避免在foreach
循环中修改集合是为了确保逻辑的正确性、避免异常和潜在的性能问题。如果需要修改,应该使用集合的迭代器或者其它安全的方法来进行。