转载http://blog.sohu.com/people/!dmFsa2ZncEBzb2h1LmNvbQ==/265878270.html
异常带来诸多好处。首先,它将错误处理代码从正常代码(normalcode)中分离出来。你可以将那些执行概率为99.9%的代码封装在一个try块 内,然后将异常处理代码----这些代码是不经常执行的----置于catch子句中。这种方式的好处是,正常代码因此而更简洁。
如果你不知 道如何处理某个方法中的一个特定错误,那么你可以在方法中抛出异常,将处理权交给其他人。如果你抛出一个检查异常(checkedexception), 那么Java编译器将强制客户程序员(cilentprogrammer)处理这个潜在异常,或者捕捉之,或者在方法的throws子句中声明之。 Java编译器确保检查异常被处理,这使得Java程序更为健壮。
何时抛出异常
异常应于何时抛出?答案归于一条原则:
如果方法遇到一个不知道如何处理的意外情况(abnormalcondition),那么它应该抛出异常。
不幸的是,虽然这条原则易于记忆和引用,但是它并不十分清晰。实际上,它引出了另一个的问题:什么是意外情况?
这是一个价值6.4万美元的问题。是否视某特殊事件为“意外情况”是一个主观决定。其依据通常并不明显。正因为如此,它才价值不菲。
一个更有用的经验法则是:
在有充足理由将某情况视为该方法的典型功能(typicalfunctioning)部分时,避免使用异常。
因此,意外情况就是指方法的“正常功能”(normalfunctioning)之外的情况。请允许我通过几个例子来说明问题。
几个例子
第一个示例使用java.io包的FileInputStream类和DataInputStream类。这是使用FileInputStream类将文件内容发送到标准输出(standardoutput)的代码:
//Insourcepacketinfileexcept/ex9/Example9a.java
importjava.io.*;
classExample9a{
publicstaticvoidmain(String[]args)
throwsIOException{
if(args.length==0){
System.out.println("Mustgivefilenameasfirstarg.");
return;
}
FileInputStreamin;
try{
in=newFileInputStream(args[0]);
}
catch(FileNotFoundExceptione){
System.out.println("Can'tfindfile:"+args[0]);
return;
}
intch;
while((ch=in.read())!=-1){
System.out.print((char)ch);
}
System.out.println();
in.close();
}
}
在本例中,FileInputStream类的read方法报告了“已到达文件末尾”的情况,但是,它并没有采用抛出异常的方式,而是返回了一个特殊 值:-1。在这个方法中,到达文件末尾被视为方法的“正常”部分,这不是意外情况。读取字节流的通常方式是,继续往下读直到达字节流末尾。
与此不同的是,DataInputStream类采取了另一种方式来报告文件末尾:
//Insourcepacketinfileexcept/ex9b/Example9b.java
importjava.io.*;
classExample9b{
publicstaticvoidmain(String[]args)
throwsIOException{
if(args.length==0){
System.out.println("Mustgivefilenameasfirstarg.");
return;
}
FileInputStreamfin;
try{
fin=newFileInputStream(args[0]);
}
/>catch(FileNotFoundExceptione){
System.out.println("Can'tfindfile:"+args[0]);
return;
}
DataInputStreamdin=newDataInputStream(fin);
try{
inti;
for(;;){
i=din.readInt();
System.out.println(i);
}
}
catch(EOFExceptione){
}
fin.close();
}
}
DataInputStream类的readInt()方法每次读取四个字节,然后将其解释为一个int型数据。当读到文件末尾时,readInt()方法将抛出EOFException。
这个方法抛出异常的原因有二。首先,readInt()无法返回一个特殊值来指示已经到达文件末尾,因为所有可能的返回值都是合法的整型数据。(例如, 它不能采用-1这个特殊值来指示文件末尾,因为-1可能就是流中的正常数据。)其次,如果readInt()在文件末尾处只读到一个、两个、或者三个字 节,那么,这就可以视为“意外情况”了。本来这个方法是要读四个字节的,但只有一到三个字节可读。由于该异常是使用这个类时的不可分割的部分,它被设计为 检查型异常(Exception类的子类)。客户程序员被强制要求处理该异常。
指示“已到达末尾”情况的第三种方式在StringTokenizer类和Stack类中得到演示:
//Insourcepacketinfileexcept/ex9b/Example9c.java
//Thisprogramprintsthewhite-spaceseparatedtokensofan
//ASCIIfileinreverseorderoftheirappearanceinthefile.
importjava.io.*;
importjava.util.*;
classExample9c{
publicstaticvoidmain(String[]args)
throwsIOException{
if(args.length==0){
System.out.println("Mustgivefilenameasfirstarg.");
return;
}
FileInputStreamin=null;
try{
in=newFileInputStream(args[0]);
}
catch(FileNotFoundExceptione){
System.out.println("Can'tfindfile:"+args[0]);
return;
}
//ReadfileintoaStringBuffer
StringBufferbuf=newStringBuffer();
try{
intch;
while((ch=in.read())!=-1){
buf.append((char)ch);
}
}
finally{
in.close();
}
//SeparateStringBufferintotokensand
//pusheachtokenintoaStack
StringTokenizertok=newStringTokenizer(buf.toString());
Stackstack=newStack();
while(tok.hasMoreTokens()){
stack.push(tok.nextToken());
}
//Printouttokensinreverseorder.
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(intindex)方法是一个带有前置条件的方法。这个方法规定客户传入的index参数的最小取值是0,最大取值是在 该String对象上调用length()方法的结果减去1。也就是说,如果字符串长度为5,那么index参数的取值限于0、1、2、3、4。
后置条件
String类的charAt(intindex)方法的后置条件要求返回值必须是该字符串对象在index位置上的字符数据,而且该字符串对象必须保持不变。
如果客户调用charAt()并传入-1、和length()一样大或者更大的值,那就认为客户没有遵守契约。这种情况下,charAt()方法是不能 正确执行的,它将抛出异常StringIndexOutOfBoundsException。该异常指出客户程序中存在某种缺陷或String类使用不 当。
如果charAt()方法接收的输入没有问题(客户遵守了契约),但是由于某种原因它无法返回指定的索引上的字符数据(没有满足后置条件),它将抛出异常来指示这种情况。这种异常指出方法的实现中包含缺陷或者方法在获得运行时资源上存在问题。
因此,如果一个事件表示了“异常条件”或者“没有遵守契约”,那么,Java程序所要做的就是抛出异常。
抛出什么?
一旦你决定抛出异常,你就要决定抛出什么异常。你可以抛出Throwable或其子类的对象。你可以抛出JavaAPI中定义的、或者自定义的Throwable对象。那么,如何决定?
通常,你只需要抛出异常,而非错误。Error是Throwable的子类,它用于指示灾难性的错误,比如OutOfMemoryError,这个错误 将由JVM报告。有时一个错误也可以被JavaAPI抛出,如java.awt.AWTError。然而,在你的代码中,你应该严格限制自己只抛出异常 (Exception的子类)。把错误的抛出留给那些大牛人。
检查型异常和非检查型异常
现在,主要问题就是抛出检查型异常还是非 检查型异常了。检查型异常是Exception的子类(或者Exception类本身),但不包括RuntimeException和它的子类。非检查型 异常是RuntimeException和它的任何子类。Error类及其子类也是检查型的,但是你应该仅着眼于异常,你所做的应该是决定抛出 RuntimeException的子类(非检查异常)还是Exception的子类(检查异常)。
如果抛出了检查型异常(而没有捕获它),那么你需要在方法的throws子句中声明该异常。客户程序员使用这个方法,他要么在其方法内捕获并处理这个异常,要么还在throws子句中抛出。检查型异常强制客户程序员对可能抛出的异常采取措施。
如果你抛出的是非检查型异常,那么客户程序员可以决定捕获与否。然而,编译器并不强制客户程序员对非检查型异常采取措施。事实上,他们甚至不知道可能这些异常。显然,在非检查型异常上客户程序员会少费些脑筋。
有一个简单的原则是:581183
如果希望客户程序员有意识地采取措施,那么抛出检查型异常。
一般而言,表示类的误用的异常应该是非检查型异常。String类的chartAt()方法抛出的 StringIndexOutOfBoundsException就是一个非检查型异常。String类的设计者并不打算强制客户程序员每次调用 charAt(intindex)时都检查index参数的合法性。
另一方面,java.io.FileInputStream类的 read()方法抛出的是IOException,这是一个检查异常。这个异常表明尝试读取文件时出错了。这并不意味着客户程序员错误地使用了 FileInputStream类,而是说这个方法无法履行它地职责,即从文件中读出下一个字节。FileInputStream类地设计者认为这个意外 情况很普遍,也很重要,因而强制客户程序员处理之。
这就是窍门所在。如果意外情况是方法无法履行职责,而你又认为它很普遍或很重要,客户程序员必须采取措施,那么抛出检查型异常。否则,抛出非检查型异常。
自定义异常类
最后,你决定实例化一个异常类,然后抛出这个异常类的实例。这里没有具体的规则。不要抛出用一条字符串信息指出意外情况的Exception类,而是自 定义一个异常类或者从已有异常类中选出一个合适的。那么,客户程序员就可以分别为不同的异常定义相应的catch语句,或者只捕获一部分。
你可能希望在异常对象中嵌入一些信息,从而告诉catch子句该异常的更详细信息。但是,你并不仅仅依赖嵌入的信息来区别不同的异常。例如,你并不希望客户程序员查询异常对象来决定问题发生在I/O上还是非法参数。
注意,String.charAt(intindex)接收一个非法输入时,它抛出的不是RuntimeException,甚至也不是 IllegalArgumentException,而是StringIndexOutOfBoundsException。这个类型名指出问题来自字符 串索引,而且这个非法索引可以通过查询这个异常对象而找出。
结论
本文的要点是,异常就是意外情况,而不该用于报告那些可以作为方法的正常功能的情况。虽然使用异常可以分离常规代码和错误处理代码,从而提高代码的可读性,但是,异常的不恰当使用会降低代码的可读性。
以下是本文提出的异常设计原则:如果方法遭遇了一个无法处理的意外情况,那么抛出一个异常。避免使用异常来指出可以视为方法的常用功能的情况。如果发现 客户违反了契约(例如,传入非法输入参数),那么抛出非检查型异常。如果方法无法履型契约,那么抛出检查型异常,也可以抛出非检查型异常。如果你认为客户 程序员需要有意识地采取措施,那么抛出检查型异常。
相关推荐
Java 异常处理习题 Java 异常处理是 Java 编程语言中的一种重要机制,用于处理程序在运行时可能出现的错误或异常情况。下面是关于 Java 异常处理的习题和知识点总结: 一、Java 异常处理关键字 * Java 中用来抛出...
Java 异常详解 Java 异常是指在程序运行中出现的违背指定正常流向的事件。异常可以分为两大类:-checked 异常和 unchecked 异常。Checked 异常必须在编译时捕捉并处理,而 unchecked 异常可以在编译时忽略,但是在...
Java 异常机制的基础知识包括异常的基础概念、异常的分类、异常的对象、异常的来源、异常的处理等几个方面。 一、Java 异常的基础知识 异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以...
本文将从 Java 异常的基本概念和语法开始,讲述 Java 异常处理的基本知识,分析 Java 异常体系结构,对比 Spring 的异常处理框架,阐述异常处理的基本原则,并提出了自己处理一个大型应用系统异常的思想,并通过设计...
Java异常处理是Java编程语言中的一个关键特性,它允许程序员优雅地处理程序运行时出现的错误情况,确保程序的稳定性和可靠性。这份“Java异常学习资料”包含了丰富的资源,帮助你深入理解Java异常机制,提升代码质量...
java异常练习题,北大青鸟学士后课后练习,主要是异常处理的小测验
Java异常处理是编程中至关重要的一个环节,它关乎程序的健壮性和稳定性。在Java中,异常(Exception)被用来表示程序运行时发生的错误或不正常情况。异常并不等同于错误,它是程序设计的一部分,允许我们优雅地处理...
Java异常处理是编程中至关重要的一个环节,它确保了程序在遇到错误情况时能够优雅地进行错误恢复或者至少提供有用的错误信息。Java异常是程序在运行时遇到的问题,例如数组越界、空指针异常、文件未找到等,这些问题...
Java异常处理是编程中至关重要的一个环节,它允许开发者优雅地处理程序运行时可能出现的错误,确保程序的健壮性和稳定性。Java中的异常处理机制基于一个独特的概念——异常(Exception),这是一种表示程序运行中...
Java异常体系结构是Java语言的重要组成部分,它负责处理程序执行过程中出现的错误情况。在Java中,异常由Throwable类及其子类构成。Throwable是所有异常的顶层父类,它有两个直接子类:Error和Exception。Error类...
Java异常处理是编程中至关重要的一个环节,它确保了程序在遇到错误时能够优雅地运行,而不是突然崩溃。本篇文章将深入探讨Java异常处理的概念、机制以及如何通过代码实例进行有效的异常管理。 Java异常处理的核心...
这个主题,"Java异常处理体系",涉及到Java如何优雅地处理程序运行时的异常,确保程序的健壮性和稳定性。这篇博文链接(已提供但无法直接访问)可能详细解释了Java异常处理的基本概念、机制以及最佳实践。 Java异常...
### 浅析JAVA异常处理机制 #### 一、Java异常处理机制概述 异常处理是Java语言中的一个重要机制,它能够确保程序在遇到不可预料的情况时仍能维持稳定运行。异常处理主要包括三个方面:捕获异常、控制程序流程以及...
Java异常处理是编程过程中的重要环节,它有助于增强程序的健壮性和稳定性。在这个全面的教程中,我们将深入探讨Java异常的基本概念、分类、处理机制以及最佳实践。 首先,Java异常是程序运行时发生的错误,它中断了...
Java异常处理设计是Java编程中一个至关重要的环节,它直接影响到程序的稳定性和可维护性。在Java中,异常处理是通过try-catch-finally语句块来实现的,主要涉及两大类异常:编译时异常(Checked Exception)和运行时...
这篇博文“Java异常框架设计”可能探讨了如何有效地利用Java的异常处理机制来构建可靠的系统。在这个讨论中,我们将深入理解Java异常的基本概念、异常分类、以及如何通过良好的框架设计提升代码的可读性和可维护性。...
在给定的“Java异常分装实例”中,我们可以学习如何创建自定义异常类、抛出和捕获异常,以及如何利用Java的异常层次结构。`exception_test`文件可能包含了一个或多个示例代码,用于演示这些概念。 首先,创建自定义...
Java异常处理主要涉及以下几个核心概念: 1. **异常(Exception)**:异常是程序在执行过程中遇到的非正常情况,它中断了正常的执行流程。Java中的异常以对象的形式存在,这些对象通常是特定异常类的实例。异常分为...