Exception
和
Error
都是继承了
Throwable
类,在
Java
中只有
Throwable
类型的实例才可以被抛出(
throw
)或者捕获(
catch
),它是异常处理机制的基本组成类型。
Exception
和
Error
体现了
Java
平台设计者对不同异常情况的分类。
Exception
是程序正常运行中,可以预料的意外情况,可能并且应该被捕获,进行相应处理。
Error
是指在正常情况下,不大可能出现的情况,绝大部分的
Error
都会导致程序(比如
JVM
自身)处于非正常的、不可恢复状态。既然是非正常情况,所以不便于也不需要捕获,常
见的比如
OutOfMemoryError
之类,都是
Error
的子类。
Exception
又分为
可检查
(
checked
)异常和
不检查
(
unchecked
)异常,可检查异常在源代码里必须显式地进行捕获处理,这是编译期检查的一部分。前面我介绍的不可查
的
Error
,是
Throwable
不是
Exception
。
不检查异常就是所谓的运行时异常,类似
NullPointerException
、
ArrayIndexOutOfBoundsException
之类,通常是可以编码避免的逻辑错误,具体根据需要来判断是否需要捕
获,并不会在编译期强制要求。
ClassNotFoundException是一个运行时异常。从类继承层次上来看,ClassNotFoundException是从Exception继承的,所以ClassNotFoundException是一个检查异常。当应用程序运行的过程中尝试使用类加载器去加载Class文件的时候,如果没有在classpath中查找到指定的类,就会抛出ClassNotFoundException。一般情况下,当我们使用Class.forName()或者ClassLoader.loadClass以及使用ClassLoader.findSystemClass()在运行时加载类的时候,如果类没有被找到,那么就会导致JVM抛出ClassNotFoundException。
NoClassDefFoundError异常,看命名后缀是一个Error。从类继承层次上看,NoClassDefFoundError是从Error继承的。和ClassNotFoundException相比,明显的一个区别是,NoClassDefFoundError并不需要应用程序去关心catch的问题。当JVM在加载一个类的时候,如果这个类在编译时是可用的,但是在运行时找不到这个类的定义的时候,JVM就会抛出一个NoClassDefFoundError错误。比如当我们在new一个类的实例的时候,如果在运行是类找不到,则会抛出一个NoClassDefFoundError的错误。
异常处理的几个基本原则
第一,
尽量不要捕获类似
Exception
这样的通用异常,而是应该捕获特定异常
,在这里是
Thread.sleep()
抛出的
InterruptedException
。
这是因为在日常的开发和合作中,我们读代码的机会往往超过写代码,软件工程是门协作的艺术,所以我们有义务让自己的代码能够直观地体现出尽量多的信息,而泛泛
的
Exception
之类,恰恰隐藏了我们的目的。另外,我们也要保证程序不会捕获到我们不希望捕获的异常。比如,你可能更希望
RuntimeException
被扩散出来,而不是被捕获。
进一步讲,除非深思熟虑了,否则不要捕获
Throwable
或者
Error
,这样很难保证我们能够正确程序处理
OutOfMemoryError
。
第二,
不要生吞(
swallow
)异常
。这是异常处理中要特别注意的事情,因为很可能会导致非常难以诊断的诡异情况。
生吞异常,往往是基于假设这段代码可能不会发生,或者感觉忽略异常是无所谓的,但是千万不要在产品代码做这种假设!
如果我们不把异常抛出来,或者也没有输出到日志(
Logger
)之类,程序可能在后续代码以不可控的方式结束。没人能够轻易判断究竟是哪里抛出了异常,以及是什么原因产生了异
常。
第三,
try-catch
代码段会产生额外的性能开销,或者换个角度说,它往往会影响
JVM
对代码进行优化,所以建议仅捕获有必要的代码段,尽量不要一个大的
try
包住整段的代码;与此同
时,利用异常控制代码流程,也不是一个好主意,远比我们通常意义上的条件语句(
if/else
、
switch
)要低效。
到底返回值变不变可以简单的这么记忆:当finally调用的任何可变API,会修改返回值;当finally调用任何的不可变API,对返回值没有影响。
总结一下:其实return与finally并没有明显的谁强谁弱。在执行时,是return语句先把返回值写入但内存中,然后停下来等待finally语句块执行完,return再执行后面的一段。