异常
摘要:
处理异常是比正常更重要的问题,就如同医生检查病人,是通过各种排除法找出正确答案的。
关键词: 常用 异常
前言:
实际编码工作中,我们应该正确的使用异常表达式代码,尽可能的使用JDK 提供的异常类,JDK 提供了很多异常的类,我们应该掌握一些常用的类,然后举一反三。
提高程序的健壮性,提高系统的可利用率,以前用函数返回值来判断个中异常,-1,-2,-3之类的容易混淆和代码的可读性差。异常机制就是区分正常执和代码错误相分离。Think in java 中说异常机制使代码阅读,编写和调试更加井井有条。
异常的理解
异常的定义
Think in java 中定义,异常情形是阻止当前方法或作用域继续执行的问题。过程就是new 一个异常对象,终止程序,以另一种方法执行下去。
异常体系
Throwable
Error Exception
OutOfMemoryError RuntimeException IOException InterruptedException
NullPointerException SqlException IllegalArgumentException
ClassNotFoundException
异常的分类
Error:
通常描述了系统级的错误,并且程序猿无法主动处理
ssertionError、OutOfMemoryError、StackOverflowError
UncheckedException:
描述运行期发生,通常由于代码问题直接引起的程序相关的错误,并且程序猿无法主动处理。
AlreadyBoundException、ClassCastException、ConcurrentModificationException、IllegalArgumentException、IllegalStateException、IndexOutOfBoundsException、JSONException、NullPointerException、SecurityException、UnsupportedOperationException
非检查异常:
JSONException:常见于json字符串解析失败的情况,但遮蔽了大量的失败细节,往往很难根据该异常作出处理。如果项目中大量使用json,建议使用第三方的json解析库,如gson等。
UnsupportedOperationException:这是一种编码上的恶性妥协,经常在抽象类的成员方法中被用户主动抛出,表示该方法还未实现等,但由于是UncheckedException,运行期才能够发现,完全无益于编码期间的安全性。自己编码时尽量不要使用。
SQLException:与JSONException原因相似,但其遮蔽的失败细节范围更广。同时,SQLException还是一个CheckedException,在不能解决问题的情况下,又使代码变的臃肿不堪。建议同。如果做Java Web开发,热门的ORM库都能解决上述问题。
CheckedException:
ClassNotFoundException、CloneNotSupportedException、FileAlreadyExistsException、FileNotFoundException、InterruptedException、IOException、SQLException、TimeoutException、UnknownHostException
描述了外部环境导致的不太严重的错误,程序猿应该主动处理。注意与系统级错误区分,系统级错误通常是不可恢复的。因此,CheckedException强制捕获或声明,程序猿必须处理。记录日志,包装后再次抛出,在方法签名中声明,是三种最常见的做法。
同UncheckedException一样,CheckedException也要保证是可控的。对CheckedException的可控性要求更高,不仅要主动检查,还要在捕获到异常时,作出合适的处理。
常见的异常
· 算术异常类:ArithmeticExecption
· · 空指针异常类:NullPointerException
· · 类型强制转换异常:ClassCastException
· · 数组负下标异常:NegativeArrayException
· · 数组下标越界异常:ArrayIndexOutOfBoundsException
· · 违背安全原则异常:SecturityException
· · 文件已结束异常:EOFException
· · 文件未找到异常:FileNotFoundException
· · 字符串转换为数字异常:NumberFormatException
· · 操作数据库异常:SQLException
· · 输入输出异常:IOException
· · 方法未找到异常:NoSuchMethodException
· · 抽象方法错误。当应用试图调用抽象方法时抛出 :java.lang.AbstractMethodError
· · 断言错, 用来指示一个断言失败的情况:java.lang.AssertionError
· · 类循环依赖错误。在初始化一个类时,若检测到类之间循环依赖则抛出该异常:java.lang.ClassCircularityError
· · 类格式错误。当Java虚拟机试图从一个文件中读取Java类,检测到该文件的内容不符合类的有效格式时抛出:java.lang.ClassFormatError
· · 错误。是所有错误的基类,用于标识严重的程序运行问题。这些问题通常描述一些不应被应用程序捕获的反常情况:java.lang.Error
· · 初始化程序错误。当执行一个类的静态初始化程序的过程中,发生了异常时抛出。静态初始化程序是指直接包含于类中的static语句段:java.lang.ExceptionInInitializerError
· · 违法访问错误。当一个应用试图访问、修改某个类的域(Field)或者调用其方法,但是又违反域或方法的可见性声明,则抛出该异常:java.lang.IllegalAccessError
· · 不兼容的类变化错误。当正在执行的方法所依赖的类定义发生了不兼容的改变时,抛出该异常。一般在修改了应用中的某些类的声明定义而没有对整个应用重新编译而直接运行的情况下,容易引发该错误:java.lang.IncompatibleClassChangeError
· · 实例化错误。当一个应用试图通过Java的new操作符构造一个抽象类或者接口时抛出该异常:java.lang.InstantiationError
· · 内部错误。用于指示Java虚拟机发生了内部错误:java.lang.InternalError
· · 链接错误。该错误及其所有子类指示某个类依赖于另外一些类,在该类编译之后,被依赖的类改变了其类定义而没有重新编译所有的类,进而引发错误的情况:java.lang.LinkageError
· · 未找到类定义错误。当Java虚拟机或者类装载器试图实例化某个类,而找不到该类的定义时抛出该错误:java.lang.NoClassDefFoundError
· · 域不存在错误。当应用试图访问或者修改某类的某个域,而该类的定义中没有该域的定义时抛出该错误:java.lang.NoSuchFieldError
· · 方法不存在错误。当应用试图调用某类的某个方法,而该类的定义中没有该方法的定义时抛出该错误:java.lang.NoSuchMethodError
· · 内存不足错误。当可用内存不足以让Java虚拟机分配给一个对象时抛出该错误:java.lang.OutOfMemoryError
· · 堆栈溢出错误。当一个应用递归调用的层次太深而导致堆栈溢出时抛出该错误:java.lang.StackOverflowError
· · 线程结束。当调用Thread类的stop方法时抛出该错误,用于指示线程结束:java.lang.ThreadDeath
· · 未知错误。用于指示Java虚拟机发生了未知严重错误的情况:java.lang.UnknownError
· · 未满足的链接错误。当Java虚拟机未找到某个类的声明为native方法的本机语言定义时抛出:java.lang.UnsatisfiedLinkError
· · 不支持的类版本错误。当Java虚拟机试图从读取某个类文件,但是发现该文件的主、次版本号不被当前Java虚拟机支持的时候,抛出该错误:java.lang.UnsupportedClassVersionError
· · 验证错误。当验证器检测到某个类文件中存在内部不兼容或者安全问题时抛出该错误:java.lang.VerifyError
· · 虚拟机错误。用于指示虚拟机被破坏或者继续执行操作所需的资源不足的情况:java.lang.VirtualMachineError
· · 算术条件异常。譬如:整数除零等:java.lang.ArithmeticException
· · 数组索引越界异常。当对数组的索引值为负数或大于等于数组大小时抛出:java.lang.ArrayIndexOutOfBoundsException
· · 数组存储异常。当向数组中存放非数组声明类型对象时抛出:java.lang.ArrayStoreException
· · 类造型异常。假设有类A和B(A不是B的父类或子类),O是A的实例,那么当强制将O构造为类B的实例时抛出该异常。该异常经常被称为强制类型转换异常 :java.lang.ClassCastException
· · 找不到类异常。当应用试图根据字符串形式的类名构造类,而在遍历CLASSPAH之后找不到对应名称的class文件时,抛出该异常:java.lang.ClassNotFoundException
· · 不支持克隆异常。当没有实现Cloneable接口或者不支持克隆方法时,调用其clone()方法则抛出该异常:java.lang.CloneNotSupportedException
· · 枚举常量不存在异常。当应用试图通过名称和枚举类型访问一个枚举对象,但该枚举对象并不包含常量时,抛出该异常:java.lang.EnumConstantNotPresentException
· · 根异常。用以描述应用程序希望捕获的情况:java.lang.Exception
· · 违法的访问异常。当应用试图通过反射方式创建某个类的实例、访问该类属性、调用该类方法,而当时又无法访问类的、属性的、方法的或构造方法的定义时抛出该异常:java.lang.IllegalAccessException
· · 违法的监控状态异常。当某个线程试图等待一个自己并不拥有的对象(O)的监控器或者通知其他线程等待该对象(O)的监控器时,抛出该异常:java.lang.IllegalMonitorStateException
· · 违法的状态异常。当在Java环境和应用尚未处于某个方法的合法调用状态,而调用了该方法时,抛出该异常:java.lang.IllegalStateException
· · 违法的线程状态异常。当县城尚未处于某个方法的合法调用状态,而调用了该方法时,抛出异常:java.lang.IllegalThreadStateException
· · 索引越界异常。当访问某个序列的索引值小于0或大于等于序列大小时,抛出该异常:java.lang.IndexOutOfBoundsException
· · 实例化异常。当试图通过newInstance()方法创建某个类的实例,而该类是一个抽象类或接口时,抛出该异常:java.lang.InstantiationException
· · 被中止异常。当某个线程处于长时间的等待、休眠或其他暂停状态,而此时其他的线程通过Thread的interrupt方法终止该线程时抛出该异常:java.lang.InterruptedException
· · 数组大小为负值异常。当使用负数大小值创建数组时抛出该异常:java.lang.NegativeArraySizeException
· · 属性不存在异常。当访问某个类的不存在的属性时抛出该异常:java.lang.NoSuchFieldException
· · 方法不存在异常。当访问某个类的不存在的方法时抛出该异常:java.lang.NoSuchMethodException
· · 空指针异常。当应用试图在要求使用对象的地方使用了null时,抛出该异常。譬如:调用null对象的实例方法、访问null对象的属性、计算null对象的长度、使用throw语句抛出null等等:java.lang.NullPointerException
· · 数字格式异常。当试图将一个String转换为指定的数字类型,而该字符串确不满足数字类型要求的格式时,抛出该异常:java.lang.NumberFormatException
· · 运行时异常。是所有Java虚拟机正常操作期间可以被抛出的异常的父类:java.lang.RuntimeException
· · 安全异常。由安全管理器抛出,用于指示违反安全情况的异常:java.lang.SecurityException
· · 字符串索引越界异常。当使用索引值访问某个字符串中的字符,而该索引值小于0或大于等于序列大小时,抛出该异常:java.lang.StringIndexOutOfBoundsException
· · 类型不存在异常:java.lang.TypeNotPresentException
异常设计原则
* 系统异常:软件的缺陷,客户端对此类异常是无能为力的,通常都是Unchecked Exception。
*业务异常:用户未按正常流程操作导致的异常,都是Checked Exception
异常的使用
异常的好处
如果你不知道如何处理某个方法中的一个特定错误,那么你可以在方法中抛出异常,将处理权交给其他人。
何时抛出异常
异常应于何时抛出?答案归于一条原则:
如果方法遇到一个不知道如何处理的意外情况(abnormal condition),那么它应该抛出异常。
这就是窍门所在。如果意外情况是方法无法履行职责,而你又认为它很普遍或很重要,客户程序员必须采取措施,那么抛出检查型异常。否则,抛出非检查型异常。
异常表示没有遵守契约
面向对象程序设计中经常讨论的一个设计方法是契约设计,它指出方法是客户(方法的调用者)和声明方法的类之间的契约。这个契约包括客户必须满足的前置条件(precondition)和方法本身必须满足的后置条件(postcondition)。
如果抛出了检查型异常(而没有捕获它),那么你需要在方法的throws子句中声明该异常。客户程序员使用这个方法,他要么在其方法内捕获并处理这个异常,要么还在throws子句中抛出。检查型异常强制客户程序员对可能抛出的异常采取措施。
如果你抛出的是非检查型异常,那么客户程序员可以决定捕获与否。然而,编译器并不强制客户程序员对非检查型异常采取措施。事实上,他们甚至不知道可能这些异常。显然,在非检查型异常上客户程序员会少费些脑筋。
原则
异常就是意外情况,而不该用于报告那些可以作为方法的正常功能的情况。虽然使用异常可以分离常规代码和错误处理代码,从而提高代码的可读性,但是,异常的不恰当使用会降低代码的可读性。
异常设计原则:
如果方法遭遇了一个无法处理的意外情况,那么抛出一个异常。
避免使用异常来指出可以视为方法的常用功能的情况。
如果发现客户违反了契约(例如,传入非法输入参数),那么抛出非检查型异常。
如果方法无法履型契约,那么抛出检查型异常,也可以抛出非检查型异常。
如果你认为客户程序员需要有意识地采取措施,那么抛出检查型异常。
异常的架构设计
从系统不同角度看异常
从系统最终用户的角度来看,系统对于用户来说就是一个黑盒,用户并不知道系统如何实现及运行,对用户而言,系统所出现的任何异常或错误,都属于系统运行时异常。所以在设计面向最终用户服务的API时,应该捕获API所有可能出现的异常,并把异常情况封装成与用户业务相近的提示信息,用户可以根据这些提示信息作出一些处理。
而对于系统开发者而言,更多的是从系统内部逻辑来看异常。有一部分异常需要内部截获处理即try catch,而另外一部分异常对于异常产生源而言无法进行有效处理,从而需要向外抛出异常以待合适的调用者进行处理。对于开发者而言,需要预见异常,并且需要考虑何时处理异常,何时抛出异常,必要时以某种方式记录或通知异常。总而言之,开发者需要通过对系统运行时可能出现的异常尽可能地处理以保证系统的正常运行,并对于无法处理的异常以一种合适的方式记录、通知、呈现以便找到发生异常的原因,从而解决或避免异常。
设计一个统一的异常处理类
一般当程序发生异常时,通常异常处理可能需要做一些通用处理,如异常日志记录、异常通知,重定向到一个统一的错误页面(如 Web 应用)等。如果这些通用异常处理放置于 catch 块中,将导致大量的重复代码,从而可能引起日志冗余、同一异常的实现多样化等问题。另外,大量异常处理程序放置于 catch 块中造成程序的高耦合性。为了解决此类问题,有必要分离出异常处理程序、统一异常处理风格、降低耦合性、增强异常处理模块的复用程度。通常的异常处理模式包括业务委托模式(Business Delegate)、前端控制器模式(Front Controller)、拦截过滤器模式(Intercepting Filter)、AOP 模式、模板方法模式等。
在web编程中,一般对控制层的异常都应该做统一处理,因为控制层向上面对用户,所以我们要在控制层捕获service所有可能出现的异常。上面也提到我们在控制层对每个service调用进行try catch显然会很繁琐而且也会导致大量重复代码,所以在遇到这种情况时我们一定要考虑引入统一异常处理机制,而很多框架也提供了这样的处理机制,如Spring的AOP,SpringMVC的 ExceptionHandler、RESTEasy的ExceptionMapper。
对于Spring MVC框架统一异常处理机制请参考:Spring MVC 中的异常处理 (handling exceptions)
对于Restful框架的统一异常处理机制请参考: RESTEasy中的通用异常处理ExceptionMapper
异常层次定义
异常层次结构应该以一种普遍通用的原则定义。为此,我们可以利用面向对象语言具备多态的性质,隐藏异常的实际实现。对于异常 service 而言,只需要捕获最基本的应用程序异常 AppException,异常处理过滤器会自动过滤实际异常类型并找到相应的异常处理器。另外,在方法的 throws 语句中勿需放入大量的检查异常;对方法调用者也不会出现混乱的 catch 块,最多可能只存在一个用于处理基本应用程序异常 AppException(委托给异常 service 处理)。
前面的章节过,应用系统异常可以从用户和开发者两个视角去考虑。因此,我们可以把异常划分为业务操作异常和系统内部运行时异常两种类型。抛出业务级异常或系统运行时异常的决策,需要与应用系统本身的架构层次相结合,考虑所要处理异常的层次。如图所示为一个典型的异常层次结构:
其中,BussinessException 属于基本业务操作异常,所有业务操作异常都继承于该类。例如,通常 UI 层或 Web 层是由系统最终用户执行业务操作驱动,因此最好抛出业务类异常。ServiceException 一般属于中间服务层异常,该层操作引起的异常一般包装成基本 ServiceException。DAOException 属于数据访问相关的基本异常。