详解Exception和Error的区别

2023-11-30 07:53:11 浏览数 (1)

什么是Exception和Error

Error

Error是java程序运行中不可预料的非正常情况,这种异常发生以后,会直接导致JVM不可处理或者不可恢复的情况。所以这种异常不可能抓取到,比如OutOfMemoryError、NoClassDefFoundError等;

Exception

Exception是java程序运行中可预料的异常情况,可以获取到这种异常,并且对这种异常进行业务外的处理。Exception又分为检查性异常和非检查性异常。

  1. 检查性异常:没有继承RuntimeException的Exception属于检查异常,这类问题在编译期就可以确定的问题,如FileNotFoundException、IOException。这类异常必须在编写代码时,使用try-catch捕获(比如:IOException异常)
  2. 非检查异常:继承了RuntimeException的Exception,也叫运行时异常,这类问题大部分属于逻辑问题,如数组越界、空指针异常,只有运行时才能知道的问题,异常在编译时不会检查。这种异常是在代码编写或者使用过程中通过规范可以避免发生的。

Exception和Error有什么区别?

可以从以下四个方面进行回答:

  • 相同点和不同点
  • 异常的分类
  • 异常处理关键字
  • 异常处理的原则

一、相同点和不同点

相同点:Exception和Error都继承了Throwable类,在 Java 中只有 Throwable 类型的实例才可以被抛出(throw)或者捕获(catch),它是异常处理机制的基本组成类型。

不同点:Exception 和 Error 体现了不同异常情况的分类。可以说Error是天灾,出现了也恢复不了;Exception是人祸,出现了可以有对应的解决措施。"Error"通常表示严重的问题,很难通过程序来处理,而"Exception"则更广泛地表示可以通过代码处理的异常情况。

二、异常的分类

异常可以分为三个主要类别:

  1. 检查异常(Checked Exception):这些异常在编译时被检查,程序员被要求显式地处理它们或者在方法签名中使用throws关键字声明它们。例如:IOException
  2. 运行时异常(Unchecked Exception):这些异常通常是由程序逻辑错误引起的,不需要在代码中显式地捕获或声明。例如:NullPointerExceptionArithmeticException
  3. 错误(Error):表示严重的问题,通常是系统级的问题,不太可能通过程序来恢复。例如:OutOfMemoryError

三、异常处理关键字

在Java中,异常处理使用以下关键字:

  1. try: 包含可能抛出异常的代码块。
  2. catch: 用于捕获并处理特定类型的异常。
  3. finally: 包含无论是否发生异常都会执行的代码块。
  4. throw: 用于手动抛出异常。
  5. throws: 用于在方法签名中声明可能抛出的异常。

四、日常开发中关于异常的处理原则

尽量不要捕获通用异常

比如直接捕获Exception或Throwable,不利于定位异常位置,这可能会隐藏程序中的潜在问题。在业务开发中更建议自定义异常,可以根据异常快速定位业务问题;

代码语言:java复制
try {
  // 业务代码
  // …
  Thread.sleep(1000L);
} catch (Exception e) {
  // Ignore it
}

在这里是 Thread.sleep() 抛出的 InterruptedException。这是因为在日常的开发和合作中,我们读代码的机会往往超过写代码

尽量不要生吞(swallow)异常

要不然会出现异常难以诊断的诡异情况。在业务开发中哪怕不抛出异常,也要在异常位置打出关键日志。

生吞异常,往往是基于假设这段代码可能不会发生,或者感觉忽略异常是无所谓的,但是千万不要在产品代码做这种假设!如果不把异常抛出来,或者也没有输出到日志(Logger)之类,程序可能在后续代码以不可控的方式结束。没人能够轻易判断究竟是哪里抛出了异常,以及是什么原因产生了异常。

代码语言:java复制
try {
   // 业务代码
   // …
} catch (IOException e) {
    e.printStackTrace();
}

这段代码是没有任何问题的,但在开发中通常都不允许这样处理。这是因为printStackTrace()采用的是标准出错(STERR)的输出选项,很难判断出到底输出到哪里去了。如果程序就会抛出异常,但是由于没有第一时间暴露出问题,堆栈信息可能非常令人费解,往往需要相对复杂的定位。

在发现问题的时候,第一时间抛出,能够更加清晰地反映问题。尤其是对于分布式系统,如果发生异常,但是无法找到堆栈轨迹(stacktrace),这纯属是为诊断设置障碍。所以,最好使用产品日志,详细地输出到日志系统里。

考虑异常捕获在产生额外的开销

异常捕获在性能角度考虑会产生额外的开销,所以也要注意尽量不要捕获非必要的代码,捕获范围尽量小。从性能角度来审视一下 Java 的异常处理机制,这里有两个可能会相对昂贵的地方:

  1. try-catch 代码段会产生额外的性能开销,或者换个角度说,它往往会影响 JVM 对代码进行优化,所以建议仅捕获有必要的代码段,尽量不要一个大的 try 包住整段的代码;与此同时,利用异常控制代码流程,也不是一个好主意,远比我们通常意义上的条件语句(if/else、switch)要低效。
  2. Java 每实例化一个 Exception,都会对当时的栈进行快照,这是一个相对比较重的操作。如果发生的非常频繁,这个开销可就不能被忽略了。
其他处理原则
  1. 捕获精确的异常:catch块中捕获特定类型的异常,以确保只处理程序能够处理的异常,而将其他异常传播到上一层。
  2. 使用finally进行清理操作: 如果有资源需要释放,最好在finally块中进行清理,以确保资源被正确释放,即使发生异常也能执行清理操作。
  3. 适度使用异常: 异常应该用于处理真正的异常情况,而不应该被用作控制流程的手段。
  4. 异常日志记录:catch块中记录异常信息,以便在调试和维护时能够更好地理解发生的问题。

补充

NoClassDefFoundError 和 ClassNotFoundException 的区别

这也是一个比较经典异常处理的面试题目。NoClassDefFoundErrorClassNotFoundException 都是 Java 中与类加载有关的异常,但它们发生的上下文和原因略有不同。

NoClassDefFoundError与ClassNotFoundException
  1. 发生场景: NoClassDefFoundError 在运行时发生,通常是在程序运行过程中,JVM 尝试加载某个类的字节码文件时发现该类的定义(Class Definition)存在,但在运行时却找不到该类的定义。 ClassNotFoundException 同样在运行时发生,它表示在运行时试图通过类的字符串名称加载类时,找不到对应的类。
  2. 原因: NoClassDefFoundError:这通常是因为在编译时存在对某个类的引用,但在运行时找不到该类的定义。可能是在编译时存在该类,但在运行时的 classpath 中找不到该类的字节码文件。 ClassNotFoundException:这通常是由于使用 Class.forName()ClassLoader.loadClass() 或类似的方法尝试加载类时,指定的类名字符串不对应于任何类。
区别总结:
  • NoClassDefFoundError 发生在运行时,表示在运行时找不到某个类的定义。NoClassDefFoundError 可能是因为在编译时存在该类,但在运行时的 classpath 中找不到该类的字节码文件。
  • ClassNotFoundException 同样在运行时发生,表示在运行时试图通过类的字符串名称加载类时找不到对应的类。ClassNotFoundException 可能是由于尝试通过类名字符串加载类时,指定的类名不对应于任何类。代码示例:
代码语言:java复制
// NoClassDefFoundError 示例
public class ExampleNoClassDefFoundError {
    public static void main(String[] args) {
        try {
            // 编译时存在类,但运行时找不到
            Class.forName("com.example.NonExistentClass");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

在上述示例中,ExampleNoClassDefFoundError 中使用 Class.forName() 尝试加载一个编译时存在但运行时找不到的类,可能导致 NoClassDefFoundError

代码语言:java复制
// ClassNotFoundException 示例
public class ExampleClassNotFoundException {
    public static void main(String[] args) {
        try {
            // 试图加载不存在的类
            ClassLoader.getSystemClassLoader().loadClass("com.example.NonExistentClass");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

在上述示例中, ExampleClassNotFoundException 中使用 ClassLoader.loadClass() 尝试加载一个不存在的类,可能导致 ClassNotFoundException

我正在参与2023腾讯技术创作特训营第三期有奖征文,组队打卡瓜分大奖!

0 人点赞