Java异常

异常表示没有遵守契约

  通过上面的例子,你应该已经初步了解到,何时应抛出异常而不是使用其他方法进行通信。若从另一个角度来看待异常,视之为“没有遵守契约”,你可能对应当怎样使用异常有更深层的理解。
  面向对象程序设计中经常讨论的一个设计方法是契约设计,它指出方法是客户(方法的调用者)和声明方法的类之间的契约。这个契约包括客户必须满足的前置条件(precondition)和方法本身必须满足的后置条件(postcondition)。
  前置条件
  String类的charAt(int index)方法是一个带有前置条件的方法。这个方法规定客户传入的index参数的最小取值是0,最大取值是在该String对象上调用length()方法的结果减去1。也就是说,如果字符串长度为5,那么index参数的取值限于0、1、2、3、4。
  后置条件
  String类的charAt(int index)方法的后置条件要求返回值必须是该字符串对象在index位置上的字符数据,而且该字符串对象必须保持不变。
  如果客户调用charAt()并传入-1、和length()一样大或者更大的值,那就认为客户没有遵守契约。这种情况下,charAt()方法是不能正确执行的,它将抛出异常StringIndexOutOfBoundsException。该异常指出客户程序中存在某种缺陷或String类使用不当。
  如果charAt()方法接收的输入没有问题(客户遵守了契约),但是由于某种原因它无法返回指定的索引上的字符数据(没有满足后置条件),它将抛出异常来指示这种情况。这种异常指出方法的实现中包含缺陷或者方法在获得运行时资源上存在问题。
  因此,如果一个事件表示了“异常条件”或者“没有遵守契约”,那么,Java程序所要做的就是抛出异常。
  
抛出什么?

  一旦你决定抛出异常,你就要决定抛出什么异常。你可以抛出Throwable或其子类的对象。你可以抛出Java API中定义的、或者自定义的Throwable对象。那么,如何决定?
  
  通常,你只需要抛出异常,而非错误。Error是Throwable的子类,它用于指示灾难性的错误,比如OutOfMemoryError,这个错误将由JVM报告。有时一个错误也可以被Java API抛出,如java.awt.AWTError。然而,在你的代码中,你应该严格限制自己只抛出异常(Exception的子类)。把错误的抛出留给那些大牛人。
  检查型异常和非检查型异常
  现在,主要问题就是抛出检查型异常还是非检查型异常了。检查型异常是Exception的子类(或者Exception类本身),但不包括RuntimeException和它的子类。非检查型异常是RuntimeException和它的任何子类。Error类及其子类也是检查型的,但是你应该仅着眼于异常,你所做的应该是决定抛出RuntimeException的子类(非检查异常)还是Exception的子类(检查异常)。
  如果抛出了检查型异常(而没有捕获它),那么你需要在方法的throws子句中声明该异常。客户程序员使用这个方法,他要么在其方法内捕获并处理这个异常,要么还在throws子句中抛出。检查型异常强制客户程序员对可能抛出的异常采取措施。
  如果你抛出的是非检查型异常,那么客户程序员可以决定捕获与否。然而,编译器并不强制客户程序员对非检查型异常采取措施。事实上,他们甚至不知道可能这些异常。显然,在非检查型异常上客户程序员会少费些脑筋。
  有一个简单的原则是:
  如果希望客户程序员有意识地采取措施,那么抛出检查型异常。
  一般而言,表示类的误用的异常应该是非检查型异常。String类的chartAt()方法抛出的StringIndexOutOfBoundsException就是一个非检查型异常。String类的设计者并不打算强制客户程序员每次调用charAt(int index)时都检查index参数的合法性。
  另一方面,java.io.FileInputStream类的read()方法抛出的是IOException,这是一个检查异常。这个异常表明尝试读取文件时出错了。这并不意味着客户程序员错误地使用了FileInputStream类,而是说这个方法无法履行它地职责,即从文件中读出下一个字节。FileInputStream类地设计者认为这个意外情况很普遍,也很重要,因而强制客户程序员处理之。
  这就是窍门所在。如果意外情况是方法无法履行职责,而你又认为它很普遍或很重要,客户程序员必须采取措施,那么抛出检查型异常。否则,抛出非检查型异常。
  自定义异常类
  最后,你决定实例化一个异常类,然后抛出这个异常类的实例。这里没有具体的规则。不要抛出用一条字符串信息指出意外情况的Exception类,而是自定义一个异常类或者从已有异常类中选出一个合适的。那么,客户程序员就可以分别为不同的异常定义相应的catch语句,或者只捕获一部分。
  你可能希望在异常对象中嵌入一些信息,从而告诉catch子句该异常的更详细信息。但是,你并不仅仅依赖嵌入的信息来区别不同的异常。例如,你并不希望客户程序员查询异常对象来决定问题发生在I/O上还是非法参数。
  注意,String.charAt(int index)接收一个非法输入时,它抛出的不是RuntimeException,甚至也不是IllegalArgumentException,而是StringIndexOutOfBoundsException。这个类型名指出问题来自字符串索引,而且这个非法索引可以通过查询这个异常对象而找出。
  
结论

  本文的要点是,异常就是意外情况,而不该用于报告那些可以作为方法的正常功能的情况。虽然使用异常可以分离常规代码和错误处理代码,从而提高代码的可读性,但是,异常的不恰当使用会降低代码的可读性。
  以下是本文提出的异常设计原则:
  • 如果方法遭遇了一个无法处理的意外情况,那么抛出一个异常。
  • 避免使用异常来指出可以视为方法的常用功能的情况。
  • 如果发现客户违反了契约(例如,传入非法输入参数),那么抛出非检查型异常。
  • 如果方法无法履型契约,那么抛出检查型异常,也可以抛出非检查型异常。
  • 如果你认为客户程序员需要有意识地采取措施,那么抛出检查型异常。

阅读更多
个人分类: Java
上一篇Java编程之URI
下一篇选择合适的java脚本语言
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭