=============================================
原文链接: java异常处理机制 转载请注明出处!
=============================================
一款高质量系统不仅仅要考虑到其功能的完备性,同时也要兼顾正确性、健壮性、可靠性、易用性、可读性(可理解性)、可扩展性、可复用性、兼容性、可移植性……而说到这里面的“健壮性”就不得不提到java的异常系统。
在开发过程中见多一些不合理使用Exception的情况,例如:
try{}包含的代码过多;
通过 catch(Excrption e)不对异常类型进行细分;
捕获了异常没有进行处理;
使用Exception对代码流程进行控制(该问题存在争议);
设计接口的时候总是在后面添加一个 throws Exception……
这样的代码也许能够使程序正常运行,在程序中也不能说是错误,只能定义为使用不合理。但对于一个高质量的系统来说,毫无疑问,这样的代码属于劣质代码!
在开发过程中,当一个方法中接收到参数之后,紧接着需要进行的就是对参数进行校验,检验参数的类型、取值范围等是否符合输入要求。那么如果校验失败,该如何进行处理呢?是直接return null?还是为参数设置一个默认值?又或者直接throws抛出异常?
如果抛出异常,是该抛出受检异常还是抛出运行异常?如果抛出运行异常,那么又改如何选择抛出异常的类型呢?要想弄明白这些问题,就需要了解java的异常系统。
java中所有的可抛出结构根据类型来划分可分为两种:
Error:虚拟机错误,此种错误的发生一般比较严重,程序中无法捕获也不需进行处理。发生该错误只有一种结果——线程退出,如果该线程是main线程,则该程序结束。如果发生该错误就需要去查找触发点,优化程序。例如:VirtualMachineError,IOError。
Exception:所有的Exception都是可以进行捕获处理的,但是根据是否需要进行强制捕获可分为受检异常(强制捕获/抛出)和运行时异常(无强制要求)。
运行时异常主要为RunTimeException及其子类,除此之外都为受检异常。
受检异常如果不进行捕获或抛出则无法进行编译。如图:
对于error,我们不用太过于关注,该错误的产生不属于程序中的问题,不受开发者控制。因此在这里不进行讨论。
对于Exception,我们需要搞清楚以下几个问题:
1、Exception从哪里来的?
2、如何处理捕获到的Exception?
3、如何抛出Exception?
关于第一个问题:Exception从哪里来的?
a)底层抛上来的。
在调用方法的时候,有些方法会抛出checkedException,这种受检异常需要进行强制处理,要么通过try catch进行捕获,要么继续上抛。
b)在本方中主动抛出的。
当该方法在执行过程的时候发现未满足正常执行的条件,这时就可以选择主动new一个Exception进行主动抛出。
例如,在对参数进行校验的时候发现参数为null,则可以主动抛出一个NullPointException;如果参数取值范围不符合要求,则可以主动抛出IllegalArgumentException。
关于第二个问题:如何处理捕获到的Exception?
对于捕获到的Exception大部分都是底层抛出的checkedException,因为对于UnChecketException没有必要捕获,即使捕获了也无法进行处理
那么对于捕获到了checkedException,我们该如何处理呢?
对该异常进行判断,如果在本方法中可以进行处理,那么就在本方法中进行处理。如果在本方法中无法处理,那么就根据抛出策略进行抛出
不需要上抛的情况:修改数据库提交事务,如果发生异常则在catch中进行事务回滚。这种情况就是可以在本方法进行事务处理,不需要进行上抛。
而本方法无法处理,需要抛出异常,那么该如何抛出呢?是抛出UnChecketException还是checketException?
关于第三个问题:如何抛出Exception? 抛出策略是什么?
这里就是问题的难点,如何定义这个抛出策略?
程序员有一个通病,总是喜欢找一个万能准则,任何问题根据该准则都能找到解决方案。可很多问题是没有明确答案的,导致在抛出策略中找不到一个万全之策。
抛出策略主要的矛盾是:当本方法无法处理异常需要进行抛出的时候,是抛出UnChecketException还是checketException?
我个人认为,先判断上层是否有办法处理,如果有办法处理则抛出CheckedException,如果没有办法处理,解决该异常,则直接抛出UnCheckedException。可依旧存在主观的问题:如何判断上层是否能够解决该异常呢?
例如:
public FileInputStream(File file) throws FileNotFoundException { ..... if(filePath == null) { throw new NullPointerException();//文件路径为null则抛出UnCheckedException } else if(file.isInvalid()) { throw new FileNotFoundException("Invalid file path");//找不到文件则抛出CheckeException } .... }
在该方法中,使用上面的抛出策略不太合适,因为没办法证明调用处可以解决FileNotFoundException而解决不了NullPointerException.....
在网上找答案的时候偶然间看到一个关于该问题的帖子,才发现原来早在十四年前就已经有一批人激烈的讨论过这个问题,并从2003年周一至讨论到2007年,持续了四年,最终被关闭讨论才结束。帖子链接:为什么 Java 中要使用 Checked Exceptions
帖子中讨论的异常激烈,各抒己见,可以称得上百家争鸣!每种观点都有存在的道理,可最终好像也没有找到答案,不过在里面有一个人的观点挺认同的:所以无法处理的异常都使用UnCheckedException进行抛出,不要花费精力去精心设计如何抛出。
他举了一些观点支持这个结论:【tianya】论据, 这里就不在此纠结了,相信每个认真思考该问题的都会有自己的看法的。
写到这里忽然有个想法,是不是我们关注的点错误了?UnCheckedException 和CheckedException的表现区别在于是不是受编译机制的检查,如果想要被编译机制检测,那么就使用CheckedException,否则使用UnCheckedException。也就是说,如果需要调用者关注该异常,那么就使用CheckedException强制调用者进行关注并处理,如果不需要调用者关注,那么就使UnCheckedException。
如果是这样的话,问题便很清晰了,在实现方法的时候,对于底层上抛的CheckedException,如果能够解决则进行捕获解决,如果解决不了就直接throws继续上抛。对于主动抛出的异常,如果需要调用者关注并处理则抛出CheckedException,如果不需要则抛出UnCheckedException。而是否需要调用者关注该异常,就有本方法的实现者来决定了,无需考虑其他。这个抛出策略还是可以的!
关于其他的,在《Effective Java》中关于异常作者提出了一个观点:1、努力使失败保持原子性。在执行过程中如果发生异常,在退出之前应该使 异常发生之前修改的数据恢复到修改之前,以免数据错误!!!这个问题还是比较严重的,值得重点关注。
---END---