1 Java中的错误与异常
所有异常对象的基类是java.lang.Throwable语言,以及它的两个子类java.lang.exception和java.lang.Error
Error描述了Java运行时系统中很少发生的内部系统错误和资源耗尽情况(例如,VirtualMachineError、LinkageError)。–不应抛出此类对象。–如果发生这样的内部错误,除了通知用户并尝试优雅地终止程序外,几乎无能为力
Exception描述了由程序引起的错误(例如FileNotFoundException、IOException)。–这些错误可以由程序捕获和处理(例如,执行备用操作或通过关闭所有文件、网络和数据库连接来正常退出)。
用户输入错误
–除了不可避免的输入错误外,一些用户不喜欢遵循指示。–例如,用户要求连接到语法错误的URL,网络层会异常。
设备错误–硬件并不总是按照您的要求进行操作。–打印机可能已关闭。–网页可能暂时不可用。–设备在执行任务的过程中经常会出现故障。
物理限制–磁盘可能已满–可用内存不足
一些典型错误
§VirtualMachineError:抛出表示Java虚拟机已损坏或已耗尽继续运行所需的资源。
–OutOfMemoryError:当Java虚拟机由于内存不足而无法分配对象,并且垃圾收集器无法提供更多内存时抛出。
–StackOverflowError:发生堆栈溢出时抛出,因为应用程序递归太深。
–internal error:引发此错误,表示JVM中发生了一些意外的内部错误。
§LinkageError:一个类对另一个类有一些依赖关系;但是,在编译前一个类之后,后一个类发生了不兼容的更改。
–NoClassDefFoundError:如果JVM或aClassLoaderinstance尝试在类的定义中加载,但找不到任何定义,则引发此错误。
3异常处理
什么是异常?
异常是在程序执行过程中出现的异常事件,它会中断程序的正常流程。
“异常”是指代码可以将错误或异常事件传递给调用它的代码的特定方式。
异常情况:如果无法以正常方式完成任务,Java允许每种方法都有一个替代的退出路径。返回一个封装错误信息的对象的方法。
–方法立即退出,不返回任何值。
–此外,不会在调用该方法的代码处继续执行;相反,异常处理机制开始搜索可以处理此特定错误条件的异常处理程序。
异常的优点是增加程序的健壮性,缺点是错误处理代码和正常代码混在一起
(2)异常分类
运行时异常和其他异常
异常层次结构也分为两个分支:
–从RuntimeException派生的异常运时异常
–其他异常
§一般规则:
–发生运行时异常是因为犯了编程错误。任何其他异常都会发生,比如一个I/O错误,发生在你的其他程序上。
从RuntimeException继承的异常包括以下问题:
–一个错误的强制转换错误型转换
–一个越界数组访问
–一个空指针访问–……
§不从RuntimeExceptioninclude继承的异常
-尝试读取超过文件结尾的内容
-尝试打开不存在的文件
-尝试查找不表示现有类的字符串的类对象
如果它是运行时异常,那是您的错误–您可以通过根据数组边界测试数组索引来避免ArrayIndexOutOfBoundsException。
–如果在使用之前检查变量是否为空,则不会发生nullpointerexception。
§一个不存在的文件如何?–不能先检查文件是否存在,然后再打开它吗?
–实际上,在检查文件是否存在后,可能会立即将其删除。因此,“存在”的概念取决于环境,而不仅仅取决于你的代码。 非运行时异常,是程序员无法完全控制的外在问题所导致的 – 即使在代码中提前加以验证(文件是否存在),也无法完全避免失效发生。
(3) Checkedand unchecked exceptions
如何处理异常? §当异常发生时,您必须捕获并处理该异常,或者通过声明您的方法引发该异常来告诉编译器您无法处理该异常,则使用您的方法的代码将必须处理该异常(如果无法处理该异常,则可以选择声明它引发该异常)。–编译器将检查我们是否完成了以下两项操作之一(catch或declare)。
§编译器不会检查错误和运行时异常–错误表示应用程序外部发生的情况,例如系统崩溃。运行时异常通常是由应用程序逻辑中的错误引起的。–在这种情况下不能做任何事情,但必须重新编写程序代码。所以编译器不会检查它们。–这些运行时异常将在开发和测试期间发现。然后我们必须重构代码来消除这些错误。
未检查的异常
§未经检查的异常:编程错误,其他不可恢复的故障(错误+运行时异常)
–程序编译不需要任何操作,但未经检查的异常将导致程序失败
§Checked exception:必须捕获或传播每个调用方都应该知道并处理的错误,否则程序将无法编译(编译器检查是否为所有已检查的异常提供了异常处理程序)
常见的未检查异常类
§ArrayIndexOutOfBoundsException:当您的代码使用数组索引时由JVM抛出,数组索引在数组的边界之外。; §NullPointerException:当代码试图在需要对象引用的地方使用空引用时,由JVM引发。
§NumberFormatException:以编程方式抛出(例如,通过整数.parseInt(0)试图将字符串转换为数字类型,但字符串的格式不正确时。
§ClassCasteException:当试图转换对象引用失败时由JVM抛出。
§IllegalArgumentException:以编程方式抛出,以指示已向方法传递了非法或不适当的参数。您可以为自己的方法重新使用此异常。§IllegalStateException:当调用一个方法时,程序不在该方法执行其任务的适当状态时,以编程方式抛出。这种情况通常发生在不按顺序调用方法时,或者可能只允许调用一次方法并尝试再次调用它。§NoClassDefFoundError:当找不到类的定义时,由JVM或类加载器抛出。
§异常处理中使用了五个关键字:
–try–catch–finally–throws–throw
§Java的异常处理包括三个操作:
–声明异常(throws)声明“本方法可能会抛出XX异常”
–抛出异常(throw)抛出XX异常
–捕获异常(try,catch,finally)捕获并处理XX异常
Unchecked异常也可以使用throws声明或try/catch进 行捕获,但大多数时候是不需要的,也不应该这么做
当要决定是采用checked exception还是unchecked exception的时候,问一个问题:“如果这种异常一旦抛出,client会做 怎样的补救?”
– 如果客户端可以通过其他的方法恢复异常,那么采用checked exception;
– 如果客户端对出现的这种异常无能为力,那么采用unchecked exception;
– 异常出现的时候,要做一些试图恢复它的动作而不要仅仅的打印它的信息。
§ 尽量使用unchecked exception来处理编程错误:因为unchecked exception不用使客户端代码显式的处理它们,它们自己会在出现的地 方挂起程序并打印出异常信息。
– 充分利用Java API中提供的丰富unchecked exception,如 NullPointerException, IllegalArgumentException和 IllegalStateException等,使用这些标准的异常类而不需亲自创建新的 异常类,使代码易于理解并避免过多消耗内存。
§ 如果client端对某种异常无能为力,可以把它转变 为一个unchecked exception,程序被挂起并返回 客户端异常信息
– 不要创建没有意义的异常,client应该从checkedexception中获取更有价值 的信息(案发现场具体是什么样子),利用异常返回的信息来明确操作失败的 原因。 – 如果client仅仅想看到异常信息,可以简单抛出一个unchecked exception: throw new RuntimeException(“Username already taken”);
§ Summary – Checked exception应该让客户端从中得到丰富的信息。 – 要想让代码更加易读,倾向于用unchecked exception来处理程序中的错误
错误可预 料,但无法预防,但可以有手段从中恢复,此时使用checked exception, 如果做 不到这一点,则使用unchecked exception
•调用者尽其所能验证输入参数,但某些超出其控制范围的情况导致操作失败。•例如,您尝试读取一个文件,但有人在您检查该文件是否存在和读取操作开始之间将其删除。•通过声明一个选中的异常,您告诉调用者预测此失败。•
–告诉调用者预测他们无法从中恢复的异常是没有意义的。
–如果用户试图读取不存在的文件,调用方可以提示他们输入新文件名。
–另一方面,如果方法由于编程错误(无效的方法参数或错误的方法实现)而失败,则应用程序无法在执行过程中修复该问题。
–它所能做的最好的事情就是记录问题并等待开发人员稍后修复它。
对特殊结果使用checkedexceptions(即,预期情况)
§对错误使用uncheckedexceptions(意外故障)
§您应该只使用未检查的异常来表示意外的失败(即错误),或者如果您希望客户机通常会编写确保异常不会发生的代码,因为有一种方便而廉价的方法来避免异常;
§否则,您应该使用checked的异常。
§ 总结: – 对于可恢复/期望恢复的情况,抛出checked异常 – 对于程序错误、不确定是否可恢复,抛出unchecked 异常 – 尽可能在checked异常中提供方法和丰富的信息,以便协助恢复
(4) 通过抛出声明已检查的异常
§如果Java方法遇到无法处理的情况,它可以抛出异常。–方法不仅会告诉Java编译器它可以返回哪些值,还会告诉编译器哪些可能出错。
–例如,试图读取文件的代码知道该文件可能不存在或可能为空。因此,试图处理文件中信息的代码需要通知编译器它可以抛出某种IOException。
在方法名处声明该方法可能抛出的异常
如何在规范中声明异常
§总是用Javadoc@throws子句记录表示特殊结果的已检查异常,指定发生该特殊结果的条件。
§Java还可能要求使用throwsdeclaration将异常包含在方法签名中。
程序员必 须在方法的spec中明确写清本方法会抛出的所有checked exception, 以便于调用该方法的client加以处理
用于表示意外失败(客户端或实现中的错误)的未经检查的异常不属于方法后条件的一部分,因此它们不应出现在@throws or throws中。§例如,规范中永远不需要提到nullpointerexception。
抛出多个checked 异常时,都要在方法签名中列出
调用抛出选中异常的方法,例如FileInputStreamconstructor。
–检测到错误并使用throw语句抛出选中的异常。
§然后您必须告诉将使用您的方法的程序员异常的可能性。如果没有处理程序捕捉到异常,则当前执行线程将终止。
不建议抛出继承自Erro的内部Java错误异常。
–任何代码都可能抛出这些异常,它们完全超出您的控制范围。
–虚拟机或运行库中发生内部错误。
§不应抛出继承自RuntimeException的未检查异常。–这些运行时错误完全由您控制。–如果您非常担心数组索引错误,您应该花时间修复它们,而不是宣传它们可能发生的可能性。–您会产生编程错误,例如[-1]=0,它会导致unchecked异常(ArrayIndexOutOfBoundsException)。
§方法必须声明它可能抛出的所有已检查异常
§如果您的方法未能如实声明所有已检查的异常,编译器将发出错误消息。
§未经检查的异常可能超出您的控制范围(错误),也可能是由最初不应允许的条件(运行时异常)导致的。这两种异常不用处理
§如果重写超类中的方法,则子类方法声明的已检查异常不能比超类方法的更一般。§可以抛出更具体的异常,或者不在子类方法中抛出任何异常。§特别是,如果超类方法根本没有抛出checked异常,那么子类也不能。
LSP是一种特殊的子类型关系定义,在编程语言中称为(强)行为子类型,LSP依赖于以下限制:
–子类型中不能加强前置条件。
–在一个子类型中,后置条件不能减弱。
–父类型的不变量必须保留在子类型中。
–子类型方法参数:逆变
–子类型中返回类型的协变。
方法:子类型的方法不应引发新的异常,除非这些异常本身是父类型的方法引发的异常的子类型。
(5) 如何抛出异常
假设您有一个方法readData,它正在读取一个文件。你的代码中发生了可怕的事情。§您可能认为这种情况非常不正常,因此希望抛出一个异常EOF exception,其描述为“输入过程中意外到达EOF的信号”
利用Exception的构造函数,将发生错误 的现场信息充分的传递给client。 § 为了捕获失败,异常的细节信息应该包含“对该异常有贡献”的所有 参数和域的值。
§ 出于安全考虑,千万不要在细节信息中包含密码、密钥以及类似的信 息!
§如果一个现有的异常类对您有效,那么抛出一个异常是很容易的:–找到一个合适的异常类–生成该类的一个对象–抛出它 §一旦方法抛出异常,它就不会返回到调用方。这意味着您不必担心伪造默认返回值或错误代码
(6) 创建异常类
§ 如果JDK提供的exception类无法充分描述你的程序发生的错误,可以 创建自己的异常类
§只需从Exception派生它,或者从异常的子类(如IOException)派生它。§通常给出一个默认构造函数和一个包含详细消息的构造函数。Throwablesuperclass的toStringmethod返回一个包含该详细消息的字符串,该字符串便于调试。
(7)捕获异常
如果在任何地方都没有发现异常,程序将终止并向控制台打印一条消息,给出异常类型和堆栈跟踪。
–GUI程序捕获异常,打印堆栈跟踪消息,然后返回到用户界面处理循环。
§要捕获异常,请设置try/catch块:
如果try block中的任何代码抛出catchclause中指定的类的异常,则–程序将跳过try块中的其余代码。–程序在catch子句中执行处理程序代码。
–如果tryblock中的任何代码都没有引发异常,则程序将跳过catch子句。无异常抛时,catch子句不执行-如果方法中的任何代码引发catch子句中指定的类型以外的类型的异常,则此方法立即退出。–希望其中一个调用方已经为该类型提供了catch子句。
处理异常的另一种选择:什么也不做,只需将异常传递给调用者。–让read方法的调用者处理–可能是最好的选择(更倾向于正确性而不是健壮性)。
§如果我们采用这种方法,那么我们必须公布这样一个事实,即该方法可能抛出一个IOException。此时需在方法签名中声明异常 §编译器严格执行throwsspecifier。如果调用引发选中异常的方法,则必须处理它或继续传递
作为一般规则,您应该捕获那些您知道如何处理的异常,而忽略那些您不知道如何处理的异常。
§在传播异常时,必须添加throws说明符,以提醒调用方可能会引发异常。
§注意:–如果编写的方法重写了不抛出异常的超类方法,则必须捕获该方法代码中每个选中的异常。
–不允许向子类方法中添加的throws说明符多于父类方法中的throws说明符。
从异常获取详细信息
§例外对象可以包含关于例外性质的信息。
§要了解有关该对象的更多信息,请尝试e.getMessage()获取详细的错误消息(如果有)§使用e.getClass().getName()获取异常对象的实际类型。
从异常获取详细信息
§异常对象可以包含关于异常性质的信息。
§要了解有关该对象的更多信息,请尝试e.getMessage()获取详细的错误消息(如果有)§使用e.getClass().getName()获取异常对象的实际类型。
(8) 重新触发和链接异常
§您可以在catch子句中抛出异常,§通常,当您想更改异常类型时,您可以这样做。§如果您构建了一个其他程序员使用的子系统,那么使用一个表示该子系统失败的异常类型是非常有意义的。–例如,ServletException,执行一个servlet的代码可能不想知道出错的细节,但它肯定想知道这个servlet有问题。
§但是,最好将原始例外设置为新例外的“原因”
§捕获异常时,可以检索原始异常。
强烈建议使用此包装技术。它允许您在子系统中抛出高级异常,而不会丢失原始故障的详细信息。
(9) finallyClause
当代码抛出异常时,它将停止处理方法中的剩余代码并退出该方法。§如果方法获取了一些只有此方法知道的资源(文件、数据库连接,…),并且必须清除该资源,则这是一个问题。§一种解决方案是捕获并重新抛出所有异常。§但是这个解决方案是乏味的,因为您需要清理正常代码和异常代码中两个位置的资源分配。 §Java有一个更好的解决方案:finally clause.
§无论是否捕捉到异常,finally子句中的代码都将执行。§在以下示例中,程序将在所有情况下关闭文件:
程序执行最终周期的三种可能情况。
§案例1:代码没有抛出异常。–程序首先执行tryblock中的所有代码。–然后,它在最终周期中执行代码。–之后,执行将继续执行最后一个周期结束后的第一个语句。–换句话说,执行通过点1、2、5和6。
§案例2:代码抛出一个在catch子句中捕获的异常。–程序执行tryblock中的所有代码,直到抛出异常为止。将跳过tryblock中的其余代码。然后,程序执行匹配catchclause中的代码,然后执行finallycleaus中的代码。–如果catchclause没有抛出异常,程序将在finallyclause之后执行第一行。–执行通过点1、3、4、5和6。–如果catchclause在“3”处抛出异常,则异常将被抛出回调用方,并且执行仅通过点1、3和5。
§Case3:代码抛出一个异常,该异常未被任何catch子句捕获。–在这里,程序执行tryblock中的所有代码,直到抛出异常。–将跳过tryblock中的其余代码。–然后,执行finally子句中的代码,并将异常返回给此方法的调用方。–执行仅通过第1点和第5点。
(10) Try with Resources(TWR)语句
如果资源属于实现自动关闭接口的类,Java SE 7为代码模式提供了一个有用的快捷方式。§该接口只有一个方法
§在最简单的变体中,try with resources语句的格式如下
–当tryblock退出时恢复关闭()将自动调用。
§阅读文件中所有单词的示例
§当block正常退出或出现异常时近距离调用()方法,就像使用了finallyblock一样。
§指定多个资源:
无论block如何退出,两个入口和出口都是关闭的。
§如果手工编程,则需要两个嵌套的try/finallystatements。
(11) 堆栈元素分析
方法调用堆栈
§典型的应用程序涉及多个级别的方法调用,这些调用由所谓的方法调用堆栈管理。
§堆栈是后进先出队列。
§结果如何?
–JVM调用main()。
–main()在调用methodA()之前推送到调用堆栈上。
–在调用methodB()之前,将methodA()推送到调用堆栈上。
–在调用methodC()之前,将methodB()推送到调用堆栈上。
–methodC()完成。
–methodB()从调用堆栈中弹出并完成。
–methodA()从调用堆栈中弹出并完成。
–main()从调用堆栈中弹出并完成。程序退出
§假设methodC()执行“除以0”操作,这将触发算术异常。§异常消息用相关的语句行号清楚地显示方法调用堆栈跟踪:
§Process:–
MethodC()触发算术异常。由于它不处理此异常,因此它立即从调用堆栈中弹出。
–MethodB()也不处理此异常并从调用堆栈中弹出。methodA()和main()方法也是。
–main()方法返回到JVM,JVM会突然终止程序并打印调用堆栈跟踪。
§当异常发生在Java方法内部时,该方法创建一个Exceptionobject并将Exceptionobject传递给JVM(即方法“抛出”一个异常)。§Exceptionobject包含异常的类型,以及异常发生时程序的状态。§JVM负责寻找异常处理程序来处理异常对象。
–它在调用堆栈中向后搜索,直到找到该Exceptionobject的特定类的匹配异常处理程序(在Java术语中,它被称为“捕获”异常)。–如果JVM在调用堆栈中的所有方法中都找不到匹配的异常处理程序,它将终止程序。
异常调用堆栈
§假设methodD()遇到异常情况并向JVM抛出一个xxxexception。§JVM在调用堆栈中向后搜索匹配的异常处理程序。
§它发现methodA()有一个XxxExceptionhandler,并将异常对象传递给处理程序。
–注意methodC()和methodB()需要在它们的方法签名中声明“throws XxxException”,以便编译程序。
§堆栈跟踪是在程序执行的特定点上所有挂起的方法调用的列表。§几乎可以肯定,每当Java程序因意外异常而终止时,都会显示堆栈跟踪列表。§您可以通过调用Throwableclass的printstacktracemethodo来访问堆栈跟踪的文本描述。§更灵活的方法是getStackTracemethod,它生成一个stacktraceelementobjects数组,您可以在程序中对其进行分析。 §stackTraceElementClass有方法获取执行代码行的文件名和行号,以及类和方法名。§tostringmethod生成一个包含所有这些信息的格式化字符串。