`

【Java核心-基础】异常

    博客分类:
  • Java
 
阅读更多

1. 异常基本分类



 

1.1 Exception vs Error

在 Java 中,Throwable、Error、Exception、RuntimeException 是常见异常的基类。

Exception 和 Error 都继承自 Throwable 类。

try {
  // 可以在此处抛出异常
  ...
} catch (Throwable e) {
  // 可以在此处捕获并处理异常
  ...
}
  • Exception 被用于表示程序正常运行中,可以预料的意外情况,可能并且应该被捕获,进行相应处理。
  • Error 被用于表示在程序正常运行中,不大可能出现的情况。绝大部分的 Error 都会导致程序处于非正常、不可恢复的状态。这类异常一般不便于也不需要捕获。

1.2 Checked Exception vs Unchecked Exception

Java异常分为受检(checked)异常和非受检(unchecked)异常。

对于Checked Exception,必须在代码里显式地捕获处理。

对于Unchecked Exception,可以根据实际业务需要决定是否在代码中显式地捕获处理。

根据 Java文档的定义,任何既不是 Error(或其子类),也不是 RuntimeException(或其子类)的异常,都是 Checked Exception。

"For the purposes of compile-time checking of exceptions, Throwable and any subclass of Throwable that is not also a subclass of either RuntimeException or Error are regarded as checked exceptions."

 

1.3 NoClassDefFoundError vs ClassNotFoundException

NoClassDefFoundError 属于 Error。产生原因是在编译时该类是存在的,但在运行时找不到该类。很可能是程序打包时漏掉了这个类。

ClassNotFoundException 属于 Exception。主要是通过Java的反射机制加载类时产生(Class.forName 方法)。很可能是类名拼写错误,或目标类不在类路径下。

 

2. 常见使用方式

  • try-with-resource:该方式可以简化对需释放(关闭)资源的处理,避免异常导致资源无法释放的情况。
  • multiple catch:该方式可在同一个代块中处理多种类型的异常,以优化代码布局。

例:

try (BufferedReader br = new BufferedReader(...);
  BufferedWriter bw = new BufferedWriter(...)) {  // try-with-resource
  ...
} catch (ExceptionA | ExceptionB e) {  // multiple catch
  ...
}

 

3. 一般使用原则

3.1 捕获特定异常,而不是通用异常(如,Exception)

显式地捕获特定异常是对业务的更精确实现,也有助于日后的维护重构(代码也是文档)。

对通用异常的捕获可能会导致误捕获意料之外的异常,但同时又缺乏对这些不速之客恰当的处理。

 

3.2 不要生吞异常

生吞异常是指捕获异常后未作任何有效的处理,而是直接忽略了异常。

生吞异常很可能导致程序出异常时诊断十分困难。

即使已经假设异常分支不可能发生,或目标异常被忽略也无所谓,也最好有相应的日志纪录。

如果实在不知道如何处理,可以直接再抛出,或构建新的异常(保留原异常信息)再抛出,由上层调用代码处理该异常。

因为上层代码的业务逻辑可能更明晰,往往有更合理的处理方案。

 

3.3 不要调用 printStackTrace() 方法来纪录异常日志

该方法是将异常错误信息输出到 标准出错流 中。

在真实的系统环境中,情况很可能复杂到无法快速判断出错信息到底输出到了哪。也就是说 标准出错流 可能被重定向到了一个你不知道的地方。

正确的做法是将异常信息输出到日志系统中。

 

3.4 尽早抛异常

尽早抛出异常可以更清晰地反映问题。如果推迟到由更深层的方法抛出异常,所得堆栈信息会更令人费解。

void func1(String fileName) {
  ...
  InputStream in = new FileInputStream(fileName);
  ...
}

void func2(String fileName) {
  Objects.requireNonNull(fileName);  // 尽早抛出异常
  ...
  InputStream in = new FileInputStream(fileName);
  ...
}

 

3.5 谨慎自定义受检异常(Checked Exception)

Checked Exception的定义初衷应该是指望方法的调用者在捕获异常后从异常中恢复。

但在实际应用中,这种情况很少见,反而是Checked Exception导致代码可读性更差,对 Lambda 或 Stream 代码不友好。

实际情况已大大偏离了Checked Exception的设计目的。

 

3.6 不要在诊断信息中包含敏感信息

设计自定义异常类时,充足的诊断信息可以帮助更快得理解问题。但是如果包含了敏感信息,可能就是潜在的安全问题。

如,用户信息一般就是不适合输出到日志中的,机器名、IP、端口等也属于敏感信息。

 

3.7 仅捕获必要的代码段

try-catch 代码段会产生额外的性能开销,往往影响JVM对代码的优化。

 

3.8 不要用异常控制代码流程

传统的 if-else、switch 流程控制方式比异常控制方式更高效。

Java每实例化一个 Exception 都会对当时的栈进行快照,开销比较重。

不过在实际使用时需要根据具体情况做取舍。在一些抽象框架性的代码中,用异常控制流程也是不错的选择。

  • 大小: 32.3 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics