- 本博客为哈工大计算机科学与技术学院大二软件构造课程的课件翻译。同时记录了部分本人上课时的学习笔记和感悟
- 该博客11800字左右,主题为6-2 错误与异常处理,已经全部更新完成
- 由于水平有限,翻译可能不是特别流畅、通顺,并且存在一定错误,观点、笔记不一定完全正确,敬请各位批评指正!
大纲
- 1、错误和Java中的异常
- 2、异常处理
- 什么是异常
- 异常的分类
- 受查异常和非受查异常
- 如何抛出一个异常
- 创建异常类
- 捕获异常
- 重新抛出和链接异常
- finally语句
- Try-with-Resources 声明
- 分析堆栈痕迹元素
- 3、总结
阅读材料
1、错误和Java中的异常
Java中的Abnormals
- 所有异常对象的基类是java.lang.Throwable,以及它的两个子类java.lang.Exeception和java.lang.Error。
Error和Exeception
- Error类描述Java运行时系统内部很少发生的系统内部错误和资源耗尽情况(例如,VirtualMachineError、LinkageError)。
- 您不应该抛出这种类型的对象。
- 如果发生这样的内部错误,除了通知用户并尝试优雅地终止程序之外,您几乎无能为力。
(内部错误:程序员通常无能为力,一旦发生,想办法让程序优雅的结束 )
- Exception类描述由程序引起的错误(例如FileNotFoundException、IOException)。
- 这些错误可以由您的程序捕获和处理(例如,执行一个备用操作或通过关闭所有文件、网络和数据库连接来优雅地退出)。
(异常:你自己程序导致的问题,可以捕获、可以处理)
- 这些错误可以由您的程序捕获和处理(例如,执行一个备用操作或通过关闭所有文件、网络和数据库连接来优雅地退出)。
各种各样的错误
- 用户输入错误
- 除了不可避免的拼写错误,一些用户喜欢开辟自己的道路,而不是遵循指示。
- 例如,如果用户请求连接到一个语法错误的URL,网络层就会报错。
- 设备错误
- 硬件并不总是做您希望它做的事情。
- 打印机可能会被关闭。
- 网页可能暂时不可用。
- 设备在执行任务的过程中经常会出现故障。
- 物理限制
- 磁盘可以填满
- 您可能会耗尽可用内存
错误
在大多数时候, 程序员不需要实 例化Error
一些典型错误
- VirtualMachineError:被抛出来表示Java虚拟机已损坏或已耗尽继续运行所需的资源。
- OutOfMemoryError:当Java虚拟机由于内存不足而无法分配对象时抛出,并且垃圾收集器无法提供更多内存。
- StackOverflowError:当堆栈溢出发生时抛出,因为应用程序递归太深。
- InternalError:被抛出来表示JVM中发生了一些意外的内部错误。
- LinkageError:一个类对另一个类有一些依赖关系;但是,后一个类在编译前一个类之后发生了不兼容的变化。
- NoClassDefFoundError:如果JVM或类加载器实例试图加载类的定义,但是找不到定义,则抛出。
3、处理异常
(既然Error我们无能为力, 那就转向关注我们能处理的Exception)
一个例子
(1)什么是异常
异常
- 异常是在程序执行过程中出现的非正常事件,它破坏了程序的正常流程。
- 异常是一种特殊的方法,通过这种方法,代码可以将错误或异常事件传递给调用它的代码。( 将错误信息传递给上层 调用者,并报告“案发现场”的信息 )
- 如果Java不能以正常的方式完成其任务,那么它允许每个方法都有一个可选的退出路径。(return之外的第二种退出途径 )
- 该方法抛出一个封装错误信息的对象。
- 该方法立即退出,并且不返回任何值。
- 此外,在调用该方法的代码处不会恢复执行;
- 相反,异常处理机制开始搜索一个可以处理这个特定错误条件的异常处理程序。(若找不 到异常处理程序,整个系统完全退出)
(2)异常分类
Excepyions 源自Throwable
在Java编程语言中,异常对象总是派生自Throwable的类的实例。
运行时异常和其他异常
- 在进行Java编程时,要关注异常层次结构。
- 异常层次结构也分为两个分支:
- – Exceptions that derive from RuntimeException 运行时异常
- – Those that do not. 其他异常
- 一般规则
- 发生Runtime Exception是因为您犯了一个编程错误。
(运行时异常:由程序员在代码里处理不当造成) - 任何其他异常的发生都是因为一件不好的事情,例如I/O错误,发生在您原本很好的程序上。(其他异常:由外部原因造成)
- 继承自Runtime Exception的异常包括以下问题:
- A bad cast
- An out-of-bounds array access
- A null pointer access
- ……
- 不从RuntimeException继承的异常包括
- 试图读取文件末尾以外的内容
- 试图打开一个不存在的文件
- 试图为不表示现有类的字符串查找类对象
- ……
RuntimeException
- 如果是RuntimeException,那就是您的错(运行时异常,是程序 源代码中引入的故障所造成的 )
- 您可以通过测试数组索引和数组边界来避免ArrayIndexOutOfBoundsException。
- 如果在使用它之前检查变量是否为null,那么NullPointerException就不会发生。
- 如果在代码中提前进行验证,这些故障就可以避免
- 一个不存在的文件怎么样?
- 您不能先检查文件是否存在,然后再打开它吗?
- 实际上,在检查文件是否存在之后,可能会立即删除该文件。因此,“存在”的概念取决于环境,而不仅仅取决于代码。(非运行时异常,是程序员无法完全控制的外在问题所导致的)
- 即使在代码中提前加以验证(文件是否存在),也无法完全避免失效发生。
(3)受查和非受查异常
(这是从异常处理机制的角度所做的分类 异常被谁check?——编译器、程序员)
同样,Java中的异常层次结构
如何处理异常?
-
当发生异常时
-
受查异常
- 你要么捕获并处理异常,要么告诉编译器你不能通过声明你的方法抛出异常来处理它,
- 然后,使用您的方法的代码将不得不处理该异常(如果无法处理该异常,则可以选择声明抛出该异常)。
- 编译器将检查我们是否完成了两件事情中的一件(catch或declare)。
-
编译器不会检查错误和运行时异常
-
非受查异常
- 错误表示在应用程序之外发生的情况,例如系统崩溃。运行时异常通常由应用程序逻辑中的错误引起。
- 在这些情况下,您不能做任何事情,但是必须重新编写程序代码。所以编译器不会检查这些
- 在这些情况下,您不能做任何事情,但是必须重新编写程序代码。所以编译器不会检查这些
非受查异常
- 非受查异常:编程错误,其他不可恢复的故障(Error+ RuntimeException)
- 程序编译不需要任何操作,但未捕获的异常将导致程序失败
- 不需要在编译的时候用try…catch等机制处理
- 从RuntimeException派生出子类型
- 可以不处理,编译没 问题,但执行时出现 就导致程序失败,代 表程序中的潜在bug
- 类似于编程语言中的 dynamic type checking
受查异常
- 受查异常:每个调用者都应该知道并处理的错误
- 必须捕获或传播,否则程序将无法编译(编译器检查是否为所有已检查的异常提供异常处理程序)
- 需要从Exception派生出子类型
- 必须捕获并指定错误 处理器handler,否则 编译无法通过
- 类似于编程语言中的 static type checking
非受查异常的例子
常见的非受查异常类
- ArrayIndexOutOfBoundsException:当代码使用数组索引时抛出,这超出了数组的范围。
- NullPointerException:当代码试图在需要对象引用的地方使用null引用时,由JVM抛出。
- 在编程和编译的时候,IDE与编 译器均不会给出任何错误提示
- NumberFormatException:当尝试将字符串转换为数字类型,但是字符串没有合适的格式时,以编程方式抛出(例如,通过Integer.parseInt())。
在编程和编译的时候,IDE与编 译器均不会给出任何错误提示
- ClassCastException:当试图强制转换对象引用失败时,由JVM抛出。
受查的异常处理操作
- 在异常处理中使用五个关键词:
- – try – catch – finally – throws – throw
- Java的异常处理包括三个操作:
- Declaring exceptions (throws) 声明“本方法可能会发生XX异常”
- Throwing an exception (throw) 抛出XX异常
- Catching an exception (try, catch, finally) 捕获并处理XX异常
受查异常的例子
A Note
已检查的异常必须使用抛出在方法签名中捕获或声明,而未检查的异常则是可选的。(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,程序被挂起并返回客户端异常信息
不同的程序员有不同的观点,关于该如何选择checked和 unchecked exceptions,在技术人员中存有很大的 争论。
- 如果客户端代码没有有用的信息,请尽量不要创建新的自定义异常
- 不要创建没有意义的异常,client应该从checked exception中获取更有价值 的信息(案发现场具体是什么样子),利用异常返回的信息来明确操作失败的 原因。
- 如果client仅仅想看到异常信息,可以简单抛出一个unchecked exception:
- 总结
- Checked exception应该让客户端从中得到丰富的信息。
- 要想让代码更加易读,倾向于用unchecked exception来处理程序中的错误
- 检查异常应该用于可预料的、但不可预防的、可以合理恢复的错误。
- 未检查的异常应该用于其他所有事情。
- 可预料但不可预防
- 调用方尽其所能来验证输入参数,但是超出其控制范围的某些条件导致操作失败。
- 例如,您尝试读取一个文件,但是在您检查它是否存在和读取操作开始之间,有人删除了它。
- 通过声明一个已检查的异常,您可以告诉调用者预期这个失败。
- 不可预防:脱离了你的程序的控制范围
- 可合理的恢复
- 告诉调用者预期他们无法从中恢复的异常是没有意义的。
- 如果用户试图从一个不存在的文件读取,调用者可以提示他们输入一个新的文件名。
- 另一方面,如果方法由于编程错误(无效的方法参数或有缺陷的方法实现)而失败,应用程序无法在执行过程中修复问题。
- 它所能做的最好的事情就是记录问题并等待开发人员在稍后的时间修复它
- 如果读文件的时候发现文件不存在了,可以让用户选择其他文件;但是如果 调用某方法时传入了错误的参数,则无论如何都无法在不中止执行的前提下进行恢复。
- 除非您抛出的异常满足上述所有条件,否则它应该使用未检查的异常。
异常设计注意事项
- 对特殊结果使用检查异常(例如。预期的情况下)
- 使用未经检查的异常发出错误信号(意外失败)
- 您应该只使用未检查的异常来指示意外的失败(例如,一个bug),或者如果您期望客户端通常会编写确保不会发生异常的代码,因为有一种方便且廉价的方法来避免异常;
- 否则,您应该使用已检查的异常。
受查异常 VS 非受查异常
受查异常 | 非受查异常 | |
---|---|---|
基本 | 必须被显式地捕获或者传递 (try-catch-finally-throw),否则编 译器无法通过 | 异常可 以不必捕获或抛出,编译器不去检查 |
异常分类 | 除RuntimeException类外,“Exception”类的所有子类以及“Error”类及其子类都为受查异常。 | RuntimeException类及其子类是未检查异常 |
处理 | 从异常发生的现场获取详细的信息,利用 异常返回的信息来明确操作失败的原因, 并加以合理的恢复处理 | 简单打印异常信息,无法再继续处理 |
表现 | 代码看起来复杂,正常逻辑代码和异常处 理代码混在一起 | 清晰简单 |
(4)通过 throws声明异常
通过 throws声明异常
- 如果遇到无法处理的情况,Java方法可以抛出异常。
- 方法不仅会告诉Java编译器它可以返回什么值,它还会告诉编译器什么地方可能出错。(“异常”也是方法和 client端之间spec的一部分,在post-condition中刻画 )
- 例如,试图读取文件的代码知道该文件可能不存在或可能为空。因此,尝试处理文件中的信息的代码需要通知编译器它可以抛出某种IOException。
- 声明你的方法可以抛出异常的地方是方法的头;头更改以反映该方法可以抛出的已检查异常。
如何在规约中声明异常
- 发出特殊结果信号的已检查异常总是用Javadoc @throw子句记录下来,指定特殊结果发生的条件。
- Java也可能要求异常包含在方法签名中,使用一个抛出声明。
- 与作为提供的类的一部分的Java方法一样,您声明您的方法可能会在方法头和spec中抛出带有异常规范的异常
- 未检查的异常用于发出意外失败的信号——客户端或实现中的bug——不是方法的后置条件的一部分,因此它们不应该出现在@throw或throw中。
- 例如,NullPointerException不需要在规范中提到。
- 如果一个方法可能抛出多个已检查的异常类型,则必须在声明中列出所有异常类。
- 当您编写自己的方法时,您不必声明方法可能抛出的每个可能抛出的对象。
- 您可以调用抛出已检查异常的方法——例如FileInputStream构造函数。
- 您将检测到一个错误并使用throw语句抛出一个已检查的异常。
- 然后,您必须告诉使用您的方法的程序员异常的可能性。
- 如果没有处理程序捕获异常,则当前执行线程终止。
不要抛出错误和未检查的异常
- 不需要抛出内部Java错误-继承自Error的Exception。
- 任何代码都可能抛出这些异常,它们完全超出了您的控制范围。
- 在虚拟机或运行时库中发生内部错误。
- 你不应该从RuntimeException继承未检查的异常。
- 这些运行时错误完全在您的控制之下。
- 如果你是如此关心数组索引错误,你应该花你的时间修复他们,而不是告诉调用者它的可能性,他们可能会发生。
- 你犯了一个编程错误,比如[-1]= 0,导致了一个未检查的异常(ArrayIndexOutOfBoundsException)。
考虑子类型多态性
- 如果从超类重写方法,则子类方法声明的已检查异常不能比超类方法的已检查异常更普遍。
- 可以抛出更具体的异常,也可以不抛出子类方法中的任何异常。
- 特别是,如果超类方法根本不抛出任何已检查的异常,子类也不可以。
参见LSP原则 (Lecture 5-2) 目标是子类型多态:客户端可用统一的方式处理不 同类型的对象,子类型可替代父类型
Liskov可替换原则(LSP)
- LSP是子类型关系的一种特殊定义,称为(强)行为子类型
- 在编程语言中,LSP依赖于以下限制:
- 前置条件不能在子类型中加强。
- 在子类型中不能削弱后置条件。超类型的不变量必须保存在子类型中。
- 抗变性方法参数的子类型子类型方法参数:逆变
- 协方差返回类型的子类型。子类型方法的返回值:协变
- 不应该抛出新的异常亚型的方法,除了那些自己异常抛出的异常的子类型的超类型的方法。
考虑到子类型多态性
(5)如何抛出异常
- 假设您有一个读取文件的方法readData。您的代码中发生了一些可怕的事情。
- 您可能会认为这种情况非常不正常,因此希望抛出一个异常EOFException,其描述是“在输入期间意外到达EOF的信号”。
- EOFException有第二个构造函数,它接受一个字符串参数。
- 您可以通过更仔细地描述异常条件来很好地利用这一点。
- 如果一个现有的异常类为你工作,抛出异常是容易的:
- 找到一个能表达错误的Exception类/ 或者构造一个新的Exception类
- 构造Exception类的实例,将错误信息写入
- 抛出它
- 一旦抛出异常,方法不会再将控制权返 回给调用它的client,因此也无需考虑返回错误代码
(6)创建一个异常类
创建一个异常类
- 您的代码可能会遇到任何标准异常类都无法充分描述的问题。
- 在这种情况下,创建自己的异常类非常容易。
- 如果JDK提供的exception类无法充分描述你的程序发生的错误,可以 创建自己的异常类
- 只需从Exception继承它,或者从Exception的一个子类(如IOException)继承它。
- 通常(编译器)会同时给出一个默认构造函数和一个包含详细消息的构造函数。
- Throwable超类的toString方法返回一个包含详细消息的字符串,这便于调试。
一个受查异常的例子
- 要定义一个检查过的异常,你需要创建一个java.lang.Exception的子类(或子类的层次结构):
- 可能抛出或传播此异常的方法必须声明它:
- 调用此方法的代码必须处理或传播此异常(或两者兼而有之):
- 有时,在某些情况下,您不希望强制每个方法在其抛出子句中声明异常实现。在这种情况下,您可以创建一个未检查的异常,它扩展了java.lang.RuntimeException。
- 方法可以抛出或传播FooRuntimeExceptionexception而无需声明它。
- 调用此方法的代码必须处理或传播此异常(或两者兼而有之):
- 有时,在某些情况下,您不希望强制每个方法在其抛出子句中声明异常实现。在这种情况下,您可以创建一个未检查的异常,它扩展了java.lang.RuntimeException。
- 方法可以抛出或传播FooRuntimeExceptionexception而无需声明它。
创建更具体的未检查异常
(7) 捕获异常
- 如果发生了在任何地方都没有捕获到的异常,程序将终止并向控制台输出一条消息,给出异常的类型和堆栈跟踪。
- GUI程序捕获异常,打印堆栈跟踪消息,然后返回到用户界面处理循环。
- 要捕获异常,设置一个try/catch块:
- 如果try块中的任何代码抛出catch子句中指定的类的异常,则
- 程序跳过try块中的其余代码。
- 程序执行catch子句中的处理程序代码
- 如果tryblock中的任何代码都没有抛出异常,那么程序就会跳过catch子句。
- 如果方法中的任何代码抛出的异常类型不是catch子句中指定的类型,则此方法立即退出。
- 希望它的一个调用者已经为该类型提供了一个catch子句。
将异常传递给调用者
- 处理异常的另一种选择是:什么也不做,只是将异常传递给调用者。
- 让read方法的调用者来操心吧!
- 可能是最好的选择(更喜欢正确性而不是健壮性)。
- 如果我们采用这种方法,那么我们就必须声明这样一个事实:该方法可能会抛出IOException。????
- 编译器严格执行抛出说明符。如果调用引发已检查异常的方法,则必须处理它或传递它
Try/catch and throw an exception?
- 一般来说,您应该捕捉那些您知道如何处理的异常,并传播那些您不知道如何处理的异常。
- 在传播异常时,必须添加一个引发说明符,以警告调用者可能会引发异常。
- 注意:
- 如果父类型中的方法没有抛出异常,那么子类型中的方法必 须捕获所有的checked exception——为什么?
- 子类型方法中不能抛出比父类型 方法更多的异常!
从异常获取详细信息
- 异常对象可能包含有关异常性质的信息。
- 要了解关于对象的更多信息,请尝试e.getMessage()获取详细的错误消息(如果有错误消息的话)
- 使用e.getClass(). getname()获取异常对象的实际类型。
捕捉多个异常
- 您可以在一个try块中捕获多个异常类型,并以不同的方式处理每个类型。
- 为每种类型使用单独的catch子句,如下例所示:
(8)重新抛出和异常链
- 您可以在catch子句中抛出异常(本来catch语句下面是 用来做exception handling的,但也可以在catch里抛出异常)
- 通常,当您想要更改异常类型时,您会这样做。
- 如果您构建了一个其他程序员使用的子系统,那么使用一个表示子系统故障的异常类型是很有意义的。
- 例如,ServletException,执行servlet的代码可能不想知道出错的细节,但它肯定想知道servlet出了错。
- 这么做的目的是:更改exception的类型,更方便client端获取错误信 息并处理
- 下面是你如何捕捉异常并重新抛出:
- 在这里,ServletException是用异常的消息文本构造的。
- 但是,最好将原始异常设置为新异常的“原因”
- 捕获异常时,可以检索原始异常
- 强烈推荐这种包装技术。它允许您在子系统中抛出高级异常,而不丢失原始故障的细节。
(9)finally子句
- 当代码抛出异常时,它将停止处理方法中的其余代码并退出该方法。
- 如果该方法获得了一些只有该方法知道的资源(文件、数据库连接……),并且必须清理该资源,这是个问题。
- 一种解决方案是捕获并重新抛出所有异常。
- 但是这个解决方案非常繁琐,因为您需要在两个地方清理资源分配——在普通代码和异常代码中。
- Java有一个更好的解决方案:finally子句。
Try-Catch-Finally
- 不管是否捕捉到异常,finally子句中的代码都会执行。
- 在下面的例子中,程序会在任何情况下关闭文件:
Try-Catch-Finally: case 1
不管程序是否碰到异常,finally都会被执行
- 程序将在三种可能的情况下执行finally子句。
- 案例1:代码没有抛出异常。
- 程序首先执行try块中的所有代码。
- 然后,它执行finally子句中的代码。
- 然后,在finally子句之后执行第一个语句。
- 换句话说,执行通过点1、2、5和6。
Try-Catch-Finally: case 2
- 案例2:代码抛出一个在catch子句中捕获的异常。
- 该程序执行try块中的所有代码,直到抛出异常为止。跳过try块中的其余代码。然后,程序执行匹配catch子句中的代码,然后执行finally子句中的代码
- 如果catch子句没有抛出异常,程序将执行finally子句后面的第一行
- 执行通过点1、3、4、5和6。
- 如果catch子句抛出一个异常,那么该异常将被返回给调用者,并且执行仅通过点1、3和5。
Try-Catch-Finally: case 3
- Case3:代码抛出一个不在任何catch子句中捕获的异常。
- 在这里,程序执行try块中的所有代码,直到抛出异常。
- 跳过try块中的其余代码。
- 然后,执行finally子句中的代码,并将异常返回给该方法的调用者。
- 执行只通过第1点和第5点。
使用不带Catch的finally
- 可以在不使用catch子句的情况下使用finally子句。
- 例如,考虑下面的try语句:
- 不管try块中是否遇到异常,都会执行finally子句中的in.close()语句。
- 如果遇到异常,则会重新抛出异常,并且必须在另一个catch子句中捕获该异常。
(10)Try-with-Resources (TWR)语句
- 在其最简单的变体中,try-with-resources语句具有这种形式
- 当try块退出时,将自动调用res.close()。
- 一个例子——读取文件中的所有单词
- 当块正常退出或出现异常时,就会调用in.close()方法,就像使用了finally块一样。
- 指定多个资源:
- 无论block如何退出,in和out都是关闭的。
- 如果是手工编写的,则需要两个嵌套的try/finally语句。
(11)堆栈元素分析
堆栈跟踪(或调用堆栈跟踪)
方法调用堆栈
- 典型的应用程序涉及到许多级别的方法调用,这些调用由一个socalled方法调用堆栈管理。
- 堆栈是后进先出队列。
- 结果是什么?
- – Enter main()
- – Enter methodA()
- – Enter methodB()
- – Enter methodC()
- – Exit methodC()
- – Exit methodB()
- – Exit methodA()
- – Exit main()
- 事情发生的先后次序
- – JVM invoke the main().
- – main() pushed onto call stack, before invoking methodA().
- – methodA() pushed onto call stack, before invoking methodB().
- – methodB() pushed onto call stack, before invoking methodC().
- – methodC() completes.
- – methodB() popped out from call stack and completes.
- – methodA() popped out from the call stack and completes.
- – main() popped out from the call stack and completes. Program exits.
调用堆栈跟踪
- 假设methodC()执行“除以0”操作,这将触发一个算术异常。
- 异常消息清楚地显示了方法调用堆栈跟踪和相关的语句行号:
- Process:
- MethodC()触发算术异常。因为它不处理这个异常,所以它立即从调用堆栈中弹出。
- MethodB()也不处理这个异常并弹出调用堆栈。methodA()和main()方法也是如此。
- main()方法返回给JVM, JVM突然终止程序并打印调用堆栈跟踪。
异常&调用堆栈
- 当Java方法内部发生异常时,该方法创建一个异常对象并将异常对象传递给JVM(即JVM)。,方法“抛出”异常)。
- Exception对象包含异常的类型和异常发生时程序的状态。
- JVM负责找到一个异常处理程序来处理异常对象。
- 它在调用堆栈中向后搜索,直到为特定的异常对象类找到匹配的异常处理程序(在Java术语中,它被称为“捕捉”异常)。
- 如果JVM在调用堆栈中的所有方法中都找不到匹配的异常处理程序,它将终止程序
- 假设methodD()遇到异常情况并向JVM抛出XxxException。
- JVM通过调用堆栈向后搜索匹配的异常处理程序。
- 它发现methodA()有一个XxxException处理程序,并将异常对象传递给该处理程序。
- 注意,为了编译程序,methodC()和methodB()需要在它们的方法签名中声明“抛出XxxException”。
分析堆栈跟踪
- 堆栈跟踪是程序执行过程中某个特定点上所有挂起的方法调用的列表。
- 几乎可以肯定地说,您已经看到了堆栈跟踪列表——每当Java程序由于未捕获的异常而终止时,都会显示堆栈跟踪列表。
- 您可以通过调用Throwable类的printStackTrace方法来访问堆栈跟踪的文本描述。
- 更灵活的方法是getStackTrace方法,它生成一个StackTraceElement对象数组,您可以在程序中对其进行分析。
- StackTraceElement类具有获取执行代码行的文件名和行号以及类和方法名的方法。
- toString方法生成一个包含所有这些信息的格式化字符串。
4 Summary
- Java中的错误和异常
- 错误处理技术
- 异常处理