Thinking in Java 4th chap12笔记-异常
1.Java的基本理念是:结构不佳的代码不能运行。发现错误的理想时机是在编译阶段,也就是在你试图运行程序之前。然而编译期间并不能找出所有的错误。余下的问题必须在运行期解决,这就需要错误源通过某种方式,把适当的信息传递给某个接收者,该接收者知道如何正确处理这个问题。
改进的错误恢复机制是提供代码健壮性最强有力的方式。错误恢复在我们编写的每个程序中都是基本的元素,但是在Java中显得格外重要。因为Java的主要目标之一是创建供他人使用的程序构件。要想创建健壮的系统,它的每一个构件都必须是健壮的。Java使用异常来提供统一一致的错误报告类型了,使得构件能够与客户端代码可靠的沟通问题。
异常处理是Java唯一正式的错误报告机制。
2.C语言以及早期语言常常具有多种错误处理模式,这些模式往往建立在约定俗成的基础上,且并不属于语言的一部分。通常会返回某个特殊值或者设置某个标志并假设接受者将对这个返回值或标志进行检查,以判定是否发生了错误。然而,随着时间的推移,人们发现,高傲的程序员在使用程序库的时候更倾向于认为:对,错误也许会发生,但是 那是别人造成的,不关我的事。所以程序员不去检查错误情形就不足为奇了。何况对于某些错误的检查确实很无聊(如C语言程序员检查printf的返回值)。如果的确在每次调用方法的时候都彻底的进行错误检查,代码可能会变的难以阅读。
解决的办法是用强制规定的形式来消除错误处理过程中的随心所欲的因素。
Exception,异常出现时,也许你不清楚该如何处理,但是你的确不应该置之不理。你要停下来,看看是否有别人或者在别的地方,能够处理这个问题。只是在当前的环境中还没有足够的信息来解决这个问题,所以就把这个问题提交到一个更高级别的环境中,在这里将作出正确的决定。
使用异常的另一个明显的好处是,它往往能够降低错误处理代码的复杂度。如果不使用异常,那么就必须检查特定的错误,并在程序的许多地方去处理它。而如果使用异常,那就不必在方法调用处进行检查,因为异常机制将保证能够捕获这个错误。并且只需要在一个地方处理错误,即所谓的异常处理程序中。这种方式不仅节省代码而且把描述在正常执行过程中做什么事情的代码和出了问题怎么办的代码相分离。总之,与之前的错误处理方法相比,异常机制使代码的阅读,编写和调试工作更加井井有条。
3. 基本异常
1.异常情形是指阻止当前方法或作用域继续执行的问题。把异常情形与普通问题区分很重要。所谓的普通问题是指在当前环境下能够得到足够的信息,总能处理这个错误。而对于异常情形,就不能继续下去了。因为在当前环境下无法获得必要的信息来解决错误。你所能做的就是从当前环境中跳出,并且把问题提交给上一级环境。这就是抛出异常发生的事情。
2. 当抛出异常后,几件事情会随之发生。1.同Java中其他对象的创建一样,将使用new在堆上创建异常对象.2.当前的执行路径被终止,它不能继续执行下去了。并且从当前环境中弹出对异常对象的引用。3.此时异常处理程序开始接管程序,并开始需要寻找一个恰当的地方来继续执行程序。这个恰当的地方就是异常处理程序,它的任务是将程序从错误状态恢复,以使程序要么换一种方式运行,要么继续运行下去。
3.异常使得我们可以每件事当做一个事务来考虑。事务是计算机中的合同法,如果出了什么问题, 我们只需放弃整个计算。我们还可以把异常看做一种undo,内建的恢复系统。因为在细心使用的情况下,我们在程序中可以拥有各种不同的恢复点。如果程序的某部分失败了,异常将"恢复"到程序的某个已知的稳定点上。
4.异常允许我们(如果没有其他手段)强制程序停止运行,并告诉我们出了什么问题,或者(理想状态下)强制程序处理问题,并返回到稳定状态。
4. 异常参数
1.所有的异常标准类都有两个构造器,一个是默认构造器,一个是接受字符串作为参数,以便能把相关信息放入异常对象的构造器。
2.关键字throw将产生许多有趣的结果。1.使用new创建了异常对象之后,此对象的引用将传给throw.尽管返回的异常对象类型通常与方法的返回烈性不同。2.还能用抛出异常的方式从当前的作用域退出。这两种情况下,将会返回一个异常对象,然后退出方法或作用域。
3.异常返回的地点与普通方法调用返回的地点完全不同。异常将在一个恰当的异常处理程序中得到解决。它的位置可能离抛出异常的位置很远,也可能会跨越方法调用栈的许多层次。
4.此外,能够抛出任意Throwable类型的异常,它是异常类型的基类。通常对于不同类型的错误,要抛出相应的异常。通常异常对象仅有的信息就是信息类型,除此之外不包含任意有意义的内容。
5. 捕获异常
1.监控区域:guarded region.它是一段可能产生异常的代码,并且后面跟着处理这些异常的代码。
2.如果在方法那内部抛出了异常,或者在方法内部调用其他方法抛出了异常,这个方法将在抛出异常的过程中结束。->try块-捕捉异常,尝试各种可能产生异常的方法调用。对于不支持异常处理的程序语言,要想仔细检查错误,就得在每个方法的调用前后加上设置和错误检查的代码,甚至在每次调用同一方法也是这样。有了异常处理机制,可以把所有的动作都放在try块中,然后只需要在一个地方就可以捕获所有异常。这意味着代码更容易编写和阅读。因为完成任务的代码没有与错误检查的代码混在一起。
3.抛出的异常必须在某处得到处理。->异常处理程序->catch.
try
{}
catch(Type1 id1)
{}
catch(Type2 id2)
{}
catch(Typ33 id3)
{}
当异常被抛出时,异常处理机制将负责搜寻参数与异常类型相匹配的第一个处理程序。然后进入catch子句执行,此时认为异常得到了处理。注意只有匹配的catch子句才能得到执行。这一点与switch语句不同,switch语句需要在每一个case后面跟一个break,以避免执行后续的case子句。注意在try块的内部,许多不同的方法调用可能产生类型相同的异常,而你只需要提供一个针对此类型的异常恢复程序。
4.终止与恢复:
1.异常处理理论上有两种基本模型。Java支持终止模型,C++亦支持。在这种模型中,将假设错误非常关键,以至于程序无法返回到异常发生的地方继续执行。一旦异常被抛出,就表明错误已经无法挽回,也不能回来继续执行。
2.另一种称为恢复模型。意思是异常处理程序的工作是修正错误,然后重新尝试调用出问题的方法。并认为第二次能成功。对于恢复模型,通常希望异常被处理后能继续执行程序。如果想要Java实现类似的恢复行为,那么在遇见错误时就不能抛出异常,而是调用方法来修正该错误。或者把try块放在while循环里,这样就不断的进入try块,直到得到满意的结果。
长久以来尽管程序员们使用的操作系统支持错误恢复模型的异常处理,但是他们最终还是转向了使用类似终止模型的代码并且忽略恢复行为。虽然恢复模型很吸引人,不过其并不实用。其中主要的原因是它可能导致的耦合:恢复性的处理程序需要了解异常抛出的地点,这势必要包含依赖于抛出位置的非通用性代码。这增加了代码编写和维护的困难,对于异常可能从很多地方抛出的大型程序来说,更是如此。
6. 自定义异常
1.要自己定义异常类,必须从已有的异常类继承。最好是选择意思相近的异常类继承,不过这样的异常并不容易找。
->extends Exception
建立新的异常类型最简单的方法就是让编译器为你产生默认的构造器,这几乎不用写多少代码。
2.对异常来说,最重要的就是部分就是类型。
3.System.err,将错误发给标准错误流,这样更容易被用户注意。System.out等也许会被重定向。System#setOut()
4.也可以为异常类定义一个接收字符串参数的构造器。注:1.可在异常堆栈信息可以看到2.通过调用e.getMessage()获取
5.printStackTrace方法在Throwable中定义,它将打印从方法调用处直到异常抛出处的方法调用序列。默认版本e.printStackTrace方法将被输错到标准错误流。->e.printStackTrace(System.out)->异常信息被发送到System.out,标准输出流。
6.异常与记录日志:
1.java.util.logging
2.重载printStackTrace->PrintWriter/StringWriter,产生字符串形式。
StringWriter trace = new StringWriter();
//重载该方法,使其产生字符串
e.printStackTrace(new PrintWriter(trace));
//severe,日志记录消息级别
logger.severe(trace.toString());
3.还可以进一步定义异常,比如加入额外的构造器和成员。不过要记住,使用程序包的客户端程序员可能仅仅只是查看一下抛出的异常类型,求他的就不管了。大多数Java库的异常就是这么用的。所以对异常添加的其他功能也许根本用不上。
7 .异常说明
1.Java鼓励人们把把方法可能抛出的异常告知使用此方法的客户端程序员,这是一种优雅的做法,它使得调用者能够确切知道写什么样的代码可以捕获所有潜在的异常。当然如果提供了源码,客户端程序员可以在源代码中查找throw语句来告知相关信息。然而程序库通常不与源码一起发布。为了预防这样的问题,Java提供了相应的语法,并强制使用这个语法,使你能以礼貌的方式告知客户端程序员某个方法可能抛出的异常类型。然后客户端程序员就可以进行相应的处理。这就是异常说明,其属于方法说明的一部分,紧跟在形式参数列表之后。
2.throws 紧跟所有潜在的异常类型的列表
3.除了从RuntimeException继承的异常,他们可以在没有异常说明的情况下被抛出。
4.代码必须与异常说明保持一致。如果你的代码产生了异常而没有进行相应处理, 编译器会发现这个问题并提醒你:要么处理这个异常,要么在异常说明中表明此方法将产生异常。->自顶向下强制执行的异常说明机制,Java在编译时就可以保证一定水平的异常的正确性。
5.作弊的地方:可以声明方法将抛出异常,实际上却不抛出。好处:为异常先占个位子,以后就可以抛出这种异常,而不用修改现有代码。定义抽象基类和接口时,这种能力很重要,这样派生类或接口实现就能够抛出这些预先声明的异常。
6.这种在编译时被强制检查的异常称为被检查的异常。
8. 捕获所有异常
1.可以只写一个异常处理程序来捕获所有类型的异常,如Exception,事实上还有其他的基类,不过Exception是同编程活动相关的基类。catch(Exception e)将捕获所有异常,所以最好把它放在处理程序列表的末尾,以防它抢在其他程序处理之前先把异常捕获了。
2.因为Exception是与编程有关的所有异常类的基类,所以它不会含有太多具体的信息。不过可以调用从其基类Throwable继承的方法:
1.getMessage()
2.getLocalizedMessage()
3.toString()
//打印调用栈轨迹,调用栈显示了“把你带到异常抛出点”的方法调用序列
4.printStackTrace()
5.printStackTrace(PrintStream)
6.printStackTrace(PrintWriter)
7.fillInStackTrace()//用于在Throwable对象的内部记录栈帧的当前状态,这在程序重新抛出错误或异常时很有用。
8.使用从Object继承的方法:getClass().getName()/getSimpleName()
3.栈轨迹:
1.printStackTrace()方法所提供的信息可以用getStackTrace()方法可以直接访问,这个方法将返回一个由栈轨迹中的元素所构成的数组。其中每一个元素都表示栈中的一帧。元素0是栈顶元素 ,并且是调用序列中的最后一个方法调用(这个Throwable被创建和抛出之处).数组中的最后一个元素和栈底是序列中的第一个方法调用。
2.注意Exception并不是一个抽象类.同Throwable
3.StackTraceElement,堆栈跟踪中的元素,它由 Throwable.getStackTrace() 返回。每个元素表示单独的一个堆栈帧.
4.重新抛出异常:有时希望把刚捕获的异常重新抛出,尤其是在使用Exception捕获所有异常的时候,既然已经得到了对当前异常对象的引用,可以直接把它重新抛出:
catch(Exception e)
{
throw e;
}
重抛异常会把异常抛给上一级环境的异常处理程序,同一个catch块的后续catch子句将被忽略。此外,异常对象的所有信息都得以保持,所以高一级环境中捕获此异常的处理程序可以从这个异常对象中得到所有信息。
1.如果只是将当前异常对象重新抛出的话,那么printStackTrace方法显示的将是原来异常抛出点的调用栈信息。而并非重新抛出点的信息。要想更新这个信息,可以调用fillInStackTrace方法,这将返回一个Throwable对象,它是通过把当前调用栈信息填入原来那个异常对象而建立的。
2.有可能在捕获异常之后抛出另一种异常,这么做的话,得到的效果类似于使用fillInStackTrace(),有关原来异常发生点的信息会丢失。剩下的是与新抛出点有关的信息。
3.永远不要为清理前一个异常对象担心,或者说为异常对象的清理担心,他们都是用new在堆上创建的对象。所以垃圾回收器会自动把他们清理掉。
4.异常链:
1.常常想要在捕获一个异常后抛出另外一个异常,并且希望把原始异常的信息保存下来。这被称为异常链。在jdk1.4以前,程序员必须自己编写代码来保存原始异常的信息。现在所有Throwable的子类在构造器都可以接受一个cause对象作为参数,这个cause用来表示原始异常,这样通过把原始异常传递给新的异常,使得即使在当前位置创建并抛出了新的异常,也能通过这个异常链追踪到异常最终发生的位置。
2.有趣的是,在Throwable的子类中,只有三种基本的异常类提供了带cause参数的构造器,它们是Error(用于Java虚拟机报告系统错误),Exception以及RuntimeException。如果要把其他类型的异常链接起来,应该使用initCause方法而不是构造器。
9. Java标准异常
1. Throwable 这个类 被用来表示任何可以作为异常抛出的类。Throwable对象可分为两种类型,指从Throwable继承而得到的类型:Error用来表示编译时和系统错误,除特殊情况外,一般不用你关心;Exception是可以被抛出的基本类型,在Jaava类库,用户方法以及运行时故障中都可能抛出Exception型异常,所以Java程序员关心的基本类型通常是Exception.
2.对异常来说,关键是理解概念及如何使用。大多数异常除了名称外其实都差不多。异常的基本概念是用名称代表发生的问题。并且异常的名称可以忘文知意。异常并非全是在java.lang包下定义的,有些异常是用来支持像util,net,io这样的程序包。这些异常可以通过他们的完整名称或者从他们的父类中可出端倪。比如所有的输入/输出异常都是从java.io.IOException继承而来的。
3.if(t == null){throw new NullPointerException;}如果必须对传递给方法的每个引用都检查其是否为null。因为无法确定调用者是否传入了非法引用。这听起来着实吓人。 幸运的是,这不必由你亲自来做。它属于Java的标准运行时检测的一部分。如果你对null引用进行调用,Java会自动抛出NullPointerException。所以上述代码是多余的。尽管你也许想要执行其他的检查以确保NullPointerException不会出现。
4.属于运行时的异常的类型有很多,他们会自动被Java虚拟机抛出,所以不必在异常说明中把他们列出来。这些异常都是从 RuntimeException 类继承而来。所以即体现了继承的优点,使用起来也很方便。这构成了一组具有相同特征和行为的异常类型且不需要在异常说明中声明方法将抛出RunTimeException类型的异常或者从RuntimeException下继承的异常。他们也被称为"不受检查"的异常.这种异常属于错误,将被自动捕获,所以不必你亲自动手了。要是自己去检查运行时异常的话,代码就显得太混乱了。不过尽管通常不用捕获RunTimeException,但是还可以在代码中抛出RunTimeException类型的异常。
5.如果不捕获这种类型的异常会发生什么事情,因为编译器没有在这个问题上对异常说明进行强行检查。RunTime类型的异常也许会穿越所有执行路径而直达main方法,而不会被捕获。
注1:对于RunTimeExcception或者从其继承的异常,是一个特例。对于这种异常类型,编译器不需要异常说明,其输出被报告了system.err.
2.所以说如果RunTimeExceptions没有被捕获而直达main,那么在程序退出前将调用异常的printStackTrace方法。
3.请务必记住,只能在代码中忽略RuntimeException及其子类型的异常,其他类型异常的处理都是由编译器强制实施的。究其原因,RuntimeException代表的是编程错误。
1.无法预料的错误。比如从你控制之外传递进来的null引用。
2.作为程序员,应该在代码中进行检查的错误。比如ArrayIndexOutofBoundsException.就得注意一下数组的大小了。在一个地方发生的异常常常会在另一个地方导致错误。
你会发现在这些情况下使用异常很有好处,他们能给调试带来便利。
4.值得注意的是,不应该把异常处理机制当做单一用途的处理工具。是的,他被用来设计一些烦人的运行时错误,这些错误往往是由于代码控制能力之外的因素导致的。然而,它对于某些编译器无法检测的编程错误,也是非常重要的。
10.使用 finally 进行清理:
1.对于一些代码,可能会希望无论try块的异常是否抛出,他们都能得到执行。这通常适用于内存回收之外的情况,因为内存的回收由垃圾回收器完成。为了达到这个效果,可以在异常处理程序后面加上finally子句。
2.无论异常是否抛出,finally子句总能被执行。
3.当Java中的异常不允许我们回到异常抛出点时,如何应对?如果把try块放到循环里面,就建立了一个“程序执行之前必须要达到”的条件。还可以加入一个static类型的计数器或者别的装置,使循环在放弃以前能尝试一定的次数, 这会使程序的健壮性更上一个台阶。
注:C++的异常处理没有finally子句,它依赖析构函数来达到清理的目的。
4.对于没有垃圾回收和析构函数自动调用机制的语言来说,finally非常重要。它能使程序员保证:无论try块发生了什么,内存总能得到释放。但Java有垃圾回收机制,所以内存释放不再是问题。而且Java也没有析构函数 可以调用,那么Java在什么情况下才能用到finally呢?
注:析构函数是当对象不再被使用的时候被调用的函数。你总能确切的知道析构函数被调用的时间和地点。C++能够自动调用析构函数,而C#,其更像Java,里面会有自动进行清理的机制。
1.当要把除内存之外的资源恢复到他们的初始状态时,就要用到finally子句。这些需要清理的资源包括:已经打开的文件或者网络连接;在屏幕上画的图形,甚至可以是外部世界的某个开关。
2.异常在没有被当前处异常处理程序捕获的情况下,异常机制也会跳到更高一层的异常程序处理之前,执行finally子句。
3.当涉及到break和continue语句的时候,finally也会执行。请注意,如果把finally子句和带标签的break和continue配合使用,在java中就没有必要使用goto语句了。
5.在return中使用finally
1.因为finally子句总会执行的,所以在一个方法中可以从多个点返回,并且可以保证重要的清理工作仍然会执行。[finally一定是在try/catch块后面,否则单独的一个finally会报错的]
6.异常丢失:缺憾。遗憾的是,Java异常的实现也有瑕疵。异常作为程序出错的标志,绝对不应该被忽略。但是它还是有可能被轻易的忽略。用某些特殊的方式使用finally子句,就会发生这个问题。
1.try块中调用一个方法f抛出了一个很重要异常。finally块中调用一个方法dispose而抛出一个无关痛痒异常。最外面的try块在捕获异常的时候只捕获到了finally块中调用方法抛出的异常,而那个很重要的异常却不见了。
这是相当严重的缺陷,因为异常可能会以一种比上面说到的以更微妙,更难以觉察到的方式完全丢失。相比之下,C++把“前一个异常还没处理就抛出下一个异常”的情形看做是糟糕的编程错误。也许在Java的未来版本中会修正这个问题(另一方面,要把所有抛出异常的方法,如上面的dispose方法,全部打包放到try-catch子句里面 注:个人认为是调用dispose方法的时候也用try/catch捕获其抛出的异常,而非直接调用).
2.另一种更加简单的丢失异常的方式是try块中调用一个方法,该方法抛出异常,然后在finally中则直接return。这样则不会抛出任何异常。更不会将堆栈信息输出到system.err.因为finally中直接return了。
11. 异常的限制:
1.当覆盖方法的时候,只能抛出在基类方法的异常说明里列出的那些异常。这个限制很有用,因为它意味着,当基类使用的代码应用到其派生类对象时,一样能够工作。当然这是面向对象的基本概念。异常也不例外。(编译时施加在异常上面的限制)
2.某抽象类Inning中,构造器和方法event都声明抛出了异常。但实际上没有抛出。这种方式使你强制用户去捕获可能在覆盖后的event版本中增加的异常,所以它很合理。同样对于抽象方法也成立。
3.异常限制对构造器并不起作用。即派生类的构造器可以抛出任何异常而不必理会基类构造器所抛出的异常。然而基类构造器必须以这样或那样的方式被调用,派生类构造器的异常声明必须包含基类构造器的异常说明。
派生类构造器不能捕获基类构造器抛出的异常
4.通过强制派生类遵守基类的异常说明,对象的可替换性得到了保障。
5.派生类方法可以不抛出任何异常,即使它是基类所定义的异常。
6.main:如果处理的是派生类的对象,编译器只会强制你捕获这个类所抛出的异常。如果将其向上转型为基类,则编译器则会正确的要求你捕获基类的异常。所有这些限制都是为了能产生更为强壮的异常处理代码。
7.尽管在继承过程中,编译器会对异常说明做强制要求,但是异常说明本身不属于方法类型的一部分。方法类型是由方法的名字与参数的类型组成的。因此不能基于异常说明来重载方法。此外,一个出现在基类方法的异常说明的异常,并不一定会出现在派生类方法的异常说明里。这一点与继承的规则明显不同。继承中,基类的方法必须出现在派生类里。换句话说,在继承和覆盖的过程中,某个特定方法的“异常说明的接口”不是变大了而是变小了。这恰好和类接口在继承时的情形相反。
注:非运行异常在throws异常声明中的时候,客户端程序员必须处理,try/catch.或者再次抛出(调用方法处增加throws)。详见 {@link CheckedException}
12. 构造器:
1.异常发生,所有的东西是否能被正确清理?大多数情况下是安全的,涉及构造器时,可能出现问题:构造器会把对象设置成安全的初始状态,但还会有别的动作,如:打开一个文件,这样的动作只有在对象使用完毕且用户调用了特殊的清理方法后才得以清理。如果在构造器抛出了异常,这些清理行为也许就不能正常工作了。-->编写构造器时要格外小心。
注:finally不能够解决此问题。因为finally每次都会执行代码,如果构造器在执行过程中半途而废,也许该对象的某些部分还 没有被成功创建,而这些部分在finally子句中却是要被清理的。
2.在设计异常时有一个问题,应该把异常全部放到这一层处理还是先处理一部分,然后再向上层抛出相同或新的异常 ,又或是不做任何处理直接向上层抛出,如果用法恰当的话,直接向上抛出的确能够简化编程。
3.对于一些文件对象所占用的系统资源,如,文件句柄.在使用完该对象之前是不应该调用dispose方法的。你可能会考虑将其放到finalize里面,但是你也不知道finalize会不会被调用,即使被调用,也不知道什么时候调用.这也是Java的一个缺陷:除了内存的清理之外,所有的清理都不会自动发生,所以你必须告诉客户端程序员,这是他们的责任.
4.对于在构造阶段可能抛出的异常,并且要求清理的类,最安全的使用方式是使用嵌套的try子句。
具体的用法大致是这样:
try
{
A a = new A();//A的构造方法可能抛出异常
//如果代码走到了这里,就说明构造成功了
try
{
a.invokeMethod();
}
catch(Exception e)
{
}
finally
{
a.dispose();//既然对象成功创建了,那么最后就要调用该方法清理下
}
}
catch(Exception e)
{
}
5.对于上面这种通用的清理惯用法在构造器不抛出异常时也应该运用。其基本规则是:在创建需要清理的对象之后,立即进入一个try-finally语句块。
13. 异常匹配:
1.抛出异常的时候,异常处理系统会按照代码书写顺序找出最近的处理程序。找到匹配的处理程序之后,它就认为程序将得到处理,然后就不再继续查找。
2.查找的时候并不要求抛出的异常同处理程序所声明的异常完全匹配,派生类的对象也可以匹配其基类的处理程序。
3.catch(A a)会捕获A以及所有从它派生的异常。这一点非常有用,如果决定在方法上加上更多派生异常的话,只要客户程序员捕获的是基类异常,那么它们的代码就无需更改。
4.如果把捕获基类的catch子句放在最前面,以此想把派生类的异常全给屏蔽掉,这样编译器会发现后面的catch子句永远也得不到执行,因为它会向你报告错误。
14.其他可选方式:
1.异常处理系统,使你能放弃程序的正常执行序列。当异常情形发生的时候,正常的执行已经变的不可能或者不需要了。异常代表了方法不能执行执行的情形。开发异常处理系统的原因是如果为每个方法所有可能发生的错误都进行处理的话,任务就显得过于繁重了,程序员不愿意这么做。结果常常是将错误忽略。应该注意到:开发异常处理的初衷是为了方便程序员处理错误。
2.异常处理的一个重要原则是:只有在你知道如何处理的情况下才捕获异常。实际上,异常处理的一个重要目标就是把错误处理的代码同错误发生的地点相分离。这样你能在一段代码中专注于要完成的事情,至于如何处理错误,则放在另一段代码完成。这样一来,主干代码就不会与错误处理逻辑混在一起,也更容易理解和维护。通过允许一个处理程序去处理多个出错点,异常处理还使得错误处理的代码趋于减少。
3.但是Java中“被检查的异常”使这个问题变的很复杂.因为它强制你在可能还没有准备好处理的时候被迫加上catch子句。[注,其实也可以在方法调用处throws,即继续向上抛出].通常这种做法会吞食异常,除非你复查代码并改正,否则异常将会丢失。虽然异常发生了,可是吞食后却完全消失了。因为编译器强迫你立刻写代码来处理异常,这种看起来做简单的做法,确可能是最糟糕的做法。
4.对于3的问题,如何处理?
1.把异常传递给控制台.对于简单的程序,最简单又不用写多少代码就能保护异常信息的方法就是把他们从main传递给控制台。注意:main作为一个方法也可以异常说明,通过把它传递给控制台,就不必在main里写try/catch子句了。
public static void main(String...args) throws Exception
2.把"被检查的异常"转换为"不检查的异常"
1.在编写你自己使用的简单程序来说,从main中抛出异常是很方便的。但这不是通用的办法。问题的实质在于:当一个普通方法调用别的方法时,要考虑到“我不知道该怎样处理这个异常,但是也不想把他们吞了”或者打印一些无用的信息。JDK1.4的异常链提供了一种新的思路来解决这个问题。可以直接把“被检查的异常”包装进RuntimeException.如:
try
{
//do something useful.code here
}
catch(IDontKnowWhatToDoWithThisCheckedException e)
{
throw new RuntimeException(e);//异常链
}
这种方法,不必吞下异常,也不必把它放到方法的异常说明说明里,而且异常链能保证你不会丢失任何原始异常的信息。
2.这种技巧给了一种选择,你可以不写try/catch子句或者异常说明,直接忽略异常,让它沿着自己的调用栈往上冒泡。同时还可以用getCause()捕获并处理特定的异常。[这样方式中,也可以创建自己的RuntimeException的子类]
15. 异常使用指南
应该在下列情况下使用异常:
1.在恰当的级别上处理问题。(在知道该如何处理的情况下才捕获异常)
2.解决问题并且重新调用产生异常的方法[注:这个...有点]
3.进行少许修补,然后绕过异常发生的地方继续执行。
4.用别的数据进行计算,以代替方法预计会返回的值。[注:方法可能未返回之前,就抛出了异常]
5.把当前运行环境下做的事情尽量做完,然后把相同的异常重抛到更高层。
6.把当前运行环境下做的事情尽量做完,然后把不同的异常重抛到更高层。
7.终止程序。
8.进行简化。(如果你的异常模式使问题变的太复杂,那用起来会非常痛苦也很烦人)[注:所以在不知道如何处理的情况下推荐抛到高层]
9.让类库和程序更加安全。(注:调试快,同时程序长期的健壮性也可以得到保证)
16. 总结:
1.异常处理的优点之一就是它使得你可以在某处集中精力处理你要解决的问题。而在另一处处理你编写的这段代码中产生的错误。其实“报告”功能才是异常的精髓所在,而所谓的报告错误并从错误中恢复[这个有点,因为只是可能将栈展开到了某个已知的稳定状态,而并没有实际执行任何种类的恢复性行为]
2.Java坚定的强调将所有的错误都以异常形式报告的这一事实,它是它远远超过诸如C++这类语言的长处之一。因为在C++这类语言中,需要以大量不同的方式来报告错误,或者根本就没有提供错误报告功能。一致的错误报告系统意味着,你再也不必对所写的每一段代码,都质问自己,错误是否正在成为漏网之鱼。你要你没有吞咽异常,这是关键所在。
部分源码:




































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































