6

【Java面试指北】Exception Error Throwable 你分得清么? - 大数据王小皮

 1 year ago
source link: https://www.cnblogs.com/shuofxz/p/16949218.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

读本篇文章之前,如果让你叙述一下 Exception Error Throwable 的区别,你能回答出来么?
你的反应是不是像下面一样呢?

  • 你在写代码时会经常 try catch(Exception)
  • 在 log 中会看到 OutOfMemoryError
  • Throwable 似乎不常见,但也大概听说过

一、Exception Error Throwable 关系#

直接看下图,展示了三者之间的关系:

  • ThrowableErrorException 的父类。
  • Exception是程序正常运行中可预料的正常情况,应该被捕获并进行处理。
    • 又分为可检查(checked)不检查(unchecked)异常。
    • 可检查异常是在编译期检查的一部分,必须显示捕获处理。如有的方法 throw exception,那么调用该函数则必须 catch 处理或者再次 throw 出去交给下一层处理。
    • 不检查异常一般指运行时异常(RuntimeException),类似 ArrayIndexOutOfBoundsExceptionArithmeticException等。一般可由代码逻辑避免,可看情况是否捕获。
  • Error 一般是正常情况下不太可能出现的,绝大部分 Error 会导致程序处于不可恢复的状态,所以也不必捕获。如 OutOfMemoryError

image.png

二、对比一个 Error 和 Exception#

你在面试中也许会被问到:

NoClassDefFoundError 和 ClassNotFoundException 有什么区别?

首先,我们看这俩名字,一个是 Error 另一个是 Exception,从上面的介绍以及看下面的继承图可以得到:ClassNotFoundException 应是编码时要被捕获的异常,NoClassDefFoundError 是编译通过了,但运行时产生的重大问题。

image.png

进一步的:
ClassNotFoundException 是运行中动态加载类时出现的问题。
举例来说,使用 Class.forName 来动态加载一个类,如果你不显示的 catch 处理,ide 都会给你提示,并且也过不了编译。

// 错误写法
public void except() {
    Class.forName("com.test.aaa");
}

// 正确写法
public void except() {
    try {
        Class.forName("com.test.aaa");
    } catch (ClassNotFoundException e) {
        // throw or log
        throw new RuntimeException(e);
        // log.error("ClassNotFoundException: ", e);
    }
}

image.png

NoClassDefFoundError 是编译时没问题,但运行时 new 实例找不到。
比如在一个类中引用另一个类的函数,编译后把另一个类的 class 文件删掉:

public class Main {
    public static void main(String[] args) {
        System.out.println("Hello world!");
        MyPrint.printName();
    }
}
public class MyPrint {
    public static void  printName() {
        System.out.println("my name is zhangsan");
    }
}

使用 javac 编译,再删除 MyPrint.class

$ tree com
com
└── shuofxz
    ├── Main.class
    ├── Main.java
    ├── MyPrint.class  # 把这个文件删掉
    └── MyPrint.java

执行程序,就会看到 NoClassDefFoundError,并且是由 ClassNotFoundException 引起的。

Exception in thread "main" java.lang.NoClassDefFoundError: com/shuofxz/MyPrint
	at com.shuofxz.Main.main(Main.java:6)
Caused by: java.lang.ClassNotFoundException: com.shuofxz.MyPrint
	at java.net.URLClassLoader.findClass(URLClassLoader.java:387)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:419)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:352)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:352)
	... 1 more

三、捕获异常代码常见问题#

一)看下面的代码有什么问题?

try {
  // 业务代码
  // ...
  Thread.sleep(1000L);
} catch (Exception e) {
}
  • 捕获了过于通用的异常 Exception,应改为对应的 InterruptedException。这么做的目的是因为:第一方便阅读代码,知道可能会出现什么具体的异常;第二不捕获意料之外的异常。
  • 不要捕获异常之后啥都不做(生吞异常)。这就是给自己挖坑,之后程序遇到问题,你很难定位到这里。

二)再看下面这个,增加了异常打印逻辑,还有什么问题不?

try {
    // 业务代码
    // …
} catch (IOException e) {
    e.printStackTrace();
}
  • 自娱自乐是 ok 的,但不要放到生产环境中。因为 e.printStackTrace() 的功能是:Prints this throwable and its backtrace to the standard error stream。你很难判断它到底输出到哪里去了。
  • 应该用成熟的日志工具如 Slf4j 等。

三)再来看下面的:

try {
    // 业务逻辑 A
    // 业务逻辑 B
    // 业务逻辑 C
    // ...
} catch (Exception e) {
    log.error("have exception", e);
}
  • 不能因为怕丢失异常捕获,就把一大段代码都框到一个 try-catch 模块中。
  • try-catch 代码段会产生额外的性能开销,它往往会影响 JVM 对代码进行优化。

四)我们前面介绍了Exception、Error、Throwable,为什么代码中经常能看到 catch XXException,却几乎看不到 catch XXErrorcatch Throwable 呢?

  • Exception 才是你应该关注处理的异常,这种异常处理后还可以使程序正常运行。
  • Error 属于重大问题,是会使程序直接崩溃的,你捕获了也没什么用,很难让程序再「活」过来。
  • 至于 Throwable,首先不应该不过这么宽泛的问题(比捕获 Exception 还严重),第二其中包含了 Error 也不是你应该处理的问题。
  • 因此,Error 和 Throwable 除非你明确知道你在干什么,否则不要捕获这两种。

三、小结#

本篇介绍了 Exception Error Throwable 的区别,并给出了相关例子帮助理解。
回到开头的问题:「叙述一下 Exception Error Throwable 的区别」你心里有数了么?


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK