转发异常处理

提示:1. 异常和错误处理的比较: 分离错误处理代码与正常处理代码java

         2. 何时抛异常:程序员

             若是方法遇到一个不知道如何处理的意外状况(abnormal condition),那么它应该抛出异常。编程

     ---》在有充足理由将某状况视为该方法的典型功能(typical functioning )部分时,避免使用异常。app

    避免使用异常来指出能够视为方法的经常使用功能的状况。 

 
post

    3.  抛什么?spa

             异常有两种:一种非检测异常,和检测异常。设计

             非检测异常,如JVM抛出的, runoutofmemory,  /0 相似的异常。code

             检测异常,告诉使用者,须要处理的。orm

             若是发现客户违反了契约(例如,传入非法输入参数),那么抛出非检查型异常。 
         若是方法没法履型契约,那么抛出检查型异常,也能够抛出非检查型异常。
         若是你认为客户程序员须要有意识地采起措施,那么抛出检查型异常。 对象

 

  如何使用异常的原则                  做者:Bill Venners著,chenkw  译 摘要   本文是设计技术专栏文章,讨论有关异常设计的问题。本文关注什么时候使用异常,并举例演示异常的恰当使用。此外,本文还提供一些异常设计的基本原则。   五个月前,我开始撰写有关设计对象的文章。本文是设计文技术系列文章的延续,讨论了有关错误报告和异常的设计原则。我假设读者已经知道什么是异常,以及异常是如何工做的。你若想回顾一下异常方面的知识,请阅读本文的姐妹篇《Java异常》。    异常的好处          异常带来诸多好处。首先,它将错误处理代码从正常代码(normal code)中分离出来。你能够将那些执行几率为99.9%的代码封装在一个try块内,而后将异常处理代码----这些代码是不常常执行的----置于catch子句中。这种方式的好处是,正常代码所以而更简洁。   若是你不知道如何处理某个方法中的一个特定错误,那么你能够在方法中抛出异常,将处理权交给其余人。若是你抛出一个检查异常(checked exception),那么Java编译器将强制客户程序员(cilent programmer)处理这个潜在异常,或者捕捉之,或者在方法的throws子句中声明之。Java编译器确保检查异常被处理,这使得Java程序更为健壮。    什么时候抛出异常   异常应于什么时候抛出?答案归于一条原则:   若是方法遇到一个不知道如何处理的意外状况(abnormal condition),那么它应该抛出异常。   不幸的是,虽然这条原则易于记忆和引用,可是它并不十分清晰。实际上,它引出了另外一个的问题:什么是意外状况?   这是一个价值6.4万美圆的问题。是否视某特殊事件为“意外状况”是一个主观决定。其依据一般并不明显。正由于如此,它才价值不菲。   一个更有用的经验法则是:   在有充足理由将某状况视为该方法的典型功能(typical functioning )部分时,避免使用异常。   所以,意外状况就是指方法的“正常功能”(normal functioning)以外的状况。请容许我经过几个例子来讲明问题。    几个例子   第一个示例使用java.io包的FileInputStream类和DataInputStream类。这是使用FileInputStream类将文件内容发送到标准输出(standard output)的代码: // In source packet in file except/ex9/Example9a.java import java.io.*; class Example9a {     public static void main(String[] args)         throws IOException {         if (args.length == 0) {             System.out.println("Must give filename as first arg.");             return;         }         FileInputStream in;         try {             in = new FileInputStream(args[0]);         }         catch (FileNotFoundException e) {             System.out.println("Can't find file: " + args[0]);             return;         }         int ch;         while ((ch = in.read()) != -1) {             System.out.print((char) ch);         }         System.out.println();         in.close();     } }   在本例中,FileInputStream类的read方法报告了“已到达文件末尾”的状况,可是,它并无采用抛出异常的方式,而是返回了一个特殊值:-1。在这个方法中,到达文件末尾被视为方法的“正常”部分,这不是意外状况。读取字节流的一般方式是,继续往下读直到达字节流末尾。   与此不一样的是,DataInputStream类采起了另外一种方式来报告文件末尾: // In source packet in file except/ex9b/Example9b.java import java.io.*; class Example9b {     public static void main(String[] args)         throws IOException {         if (args.length == 0) {             System.out.println("Must give filename as first arg.");             return;         }         FileInputStream fin;         try {             fin = new FileInputStream(args[0]);         }         catch (FileNotFoundException e) {             System.out.println("Can't find file: " + args[0]);             return;         }         DataInputStream din = new DataInputStream(fin);         try {             int i;             for (;;) {                 i = din.readInt();                 System.out.println(i);             }         }         catch (EOFException e) {         }         fin.close();     } }   DataInputStream类的readInt()方法每次读取四个字节,而后将其解释为一个int型数据。当读到文件末尾时,readInt()方法将抛出EOFException。   这个方法抛出异常的缘由有二。首先,readInt()没法返回一个特殊值来指示已经到达文件末尾,由于全部可能的返回值都是合法的整型数据。(例如,它不能采用-1这个特殊值来指示文件末尾,由于-1可能就是流中的正常数据。)其次,若是readInt()在文件末尾处只读到一个、两个、或者三个字节,那么,这就能够视为“意外状况”了。原本这个方法是要读四个字节的,但只有一到三个字节可读。因为该异常是使用这个类时的不可分割的部分,它被设计为检查型异常(Exception类的子类)。客户程序员被强制要求处理该异常。   指示“已到达末尾”状况的第三种方式在StringTokenizer类和Stack类中获得演示: // In source packet in file except/ex9b/Example9c.java // This program prints the white-space separated tokens of an // ASCII file in reverse order of their appearance in the file. import java.io.*; import java.util.*; class Example9c {     public static void main(String[] args)         throws IOException {         if (args.length == 0) {             System.out.println("Must give filename as first arg.");             return;         }         FileInputStream in = null;         try {             in = new FileInputStream(args[0]);         }         catch (FileNotFoundException e) {             System.out.println("Can't find file: " + args[0]);             return;         }         // Read file into a StringBuffer         StringBuffer buf = new StringBuffer();         try {             int ch;             while ((ch = in.read()) != -1) {                 buf.append((char) ch);             }         }         finally {             in.close();         }         // Separate StringBuffer into tokens and         // push each token into a Stack         StringTokenizer tok = new StringTokenizer(buf.toString());         Stack stack = new Stack();         while (tok.hasMoreTokens()) {             stack.push(tok.nextToken());         }         // Print out tokens in reverse order.         while (!stack.empty()) {             System.out.println((String) stack.pop());         }     } }   上面的程序逐字节读取文件,将字节数据转换为字符数据,而后将字符数据放到StringBuffer中。它使用StringTokenizer类提取以空白字符为分隔符的token(这里是一个字符串),每次提取一个并压入Stack中。最后,全部token都被从Stack中弹出并打印,每行打印一个。由于Stack类实现的是后进先出(LIFO)栈,因此,打印出来的数据顺序和文件中的数据顺序恰好相反。   StringTokenizer类和Stack类都必须可以指示“已到达末尾”状况。StringTokenizer的构造方法接纳源字符串。每一次调用nextToken()方法都将返回一个字符串,它是源字符串的下一个token。源字符串的全部token都必然会被消耗掉,StringTokenizer类必须经过某种方式指示已经没有更多的token供返回了。这种状况下,原本是能够用一个特殊的值null来指示没有更多token的。可是,此类的设计者采用了另外一个办法。他提供了一个额外的方法hasMoreTokens(),该方法返回一个布尔值来指示是否已到达末尾。每次调用nextToken()方法以前,你必须先调用hasMoreTokens()。   这种方法代表设计者并不认为到达token流的末尾是意外状况。相反,它是使用这个类的常规状况。然而,若是你在调用nextToken()以前不检查hasMoreTokens(),那么你最后会获得一个异常NoSuchElementException。虽然该异常在到达token流末尾时抛出,但它倒是一个非检查异常(RuntimeException的子类)。该异常的抛出不是为了指示“已到达末尾”,而是指示一个软件缺陷----你并无正确地使用该类。   与此相似,Stack类有一个相似的方法empty(),这个方法返回一个布尔值指示栈已经为空。每次调用pop()以前,你都必须先调用empty()方法。若是你忘了调用empty()方法,而直接在一个空栈上调用pop()方法,那么,你将获得一个异常EmptyStackException。虽然该异常是栈已经为空的状况下抛出的,但它也是一个非检查异常。它的做用不是检测空栈,而是指示客户代码中的一个软件缺陷(Stack类的不恰当使用)。    异常表示没有遵照契约   经过上面的例子,你应该已经初步了解到,什么时候应抛出异常而不是使用其余方法进行通讯。若从另外一个角度来看待异常,视之为“没有遵照契约”,你可能对应当怎样使用异常有更深层的理解。   面向对象程序设计中常常讨论的一个设计方法是契约设计,它指出方法是客户(方法的调用者)和声明方法的类之间的契约。这个契约包括客户必须知足的前置条件(precondition)和方法自己必须知足的后置条件(postcondition)。   前置条件   String类的charAt(int index)方法是一个带有前置条件的方法。这个方法规定客户传入的index参数的最小取值是0,最大取值是在该String对象上调用length()方法的结果减去1。也就是说,若是字符串长度为5,那么index参数的取值限于0、一、二、三、4。   后置条件   String类的charAt(int index)方法的后置条件要求返回值必须是该字符串对象在index位置上的字符数据,并且该字符串对象必须保持不变。   若是客户调用charAt()并传入-一、和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。这个类型名指出问题来自字符串索引,并且这个非法索引能够经过查询这个异常对象而找出。    结论   本文的要点是,异常就是意外状况,而不应用于报告那些能够做为方法的正常功能的状况。虽然使用异常能够分离常规代码和错误处理代码,从而提升代码的可读性,可是,异常的不恰当使用会下降代码的可读性。    如下是本文提出的异常设计原则:      若是方法遭遇了一个没法处理的意外状况,那么抛出一个异常。       避免使用异常来指出能够视为方法的经常使用功能的状况。        若是发现客户违反了契约(例如,传入非法输入参数),那么抛出非检查型异常。        若是方法没法履型契约,那么抛出检查型异常,也能够抛出非检查型异常。        若是你认为客户程序员须要有意识地采起措施,那么抛出检查型异常。   关于做者   Bill Venners拥有长达12年的软件从业经验。他以Artima软件公司的名义在硅谷提供软件咨询和培训服务。他精通不一样平台上的多种语言,包括针对微处理器的汇编程序设计、Unix上的C编程、Windows上的C++编程、和Web上的Java开发,所开发的软件覆盖了电子、教育、半导体和人身保险等行业。他是《深刻Java虚拟机》的做者。

相关文章
相关标签/搜索