Scala编程(第5版)
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

7.4 用try表达式实现异常处理

Scala的异常处理与其他语言类似。除了正常地返回某个值,方法也可以通过抛出异常来终止执行。方法的调用方要么捕获并处理这个异常,要么自我终止,让异常传播给更上层的调用方。异常通过这种方式传播,逐个展开调用栈,直到某个方法处理了该异常或者没有更多方法了为止。

抛出异常

在Scala中抛出异常与在Java中抛出异常看上去一样。你需要创建一个异常对象,然后用throw关键字将它抛出:

虽然看上去有些自相矛盾,但是在Scala中,throw是一个有结果类型的表达式。下面是一个带有结果类型的示例:

在这段代码中,如果n是偶数,half将被初始化成n的一半。如果n不是偶数,则在half被初始化之前,就会有异常被抛出。因此,我们可以安全地将抛出异常当作任何类型的值来对待。任何想要使用throw给出返回值的上下文都没有机会真正使用它,也就不必担心有其他问题。

从技术上讲,抛出异常这个表达式的类型是Nothing。即使表达式从不实际被求值,也可以用throw。这个技术细节听上去有些奇怪,不过在这样的场景下,还是很常见且很有用的。if表达式的一个分支用于计算出某个值,而另一个分支用于抛出异常并计算出Nothing。整个if表达式的类型就是那个计算出某个值的分支的类型。我们将在17.3节对Nothing做进一步的介绍。

捕获异常

可以用示例7.11中的语法来捕获异常。catch子句的语法之所以是这样的,是为了与Scala的一个重要组成部分,即模式匹配pattern matching),保持一致。我们将在本章简单介绍并在第13章详细介绍模式匹配这个强大的功能。

这个try-catch表达式与其他带有异常处理功能的语言一样。首先,代码体会被执行,如果抛出异常,则会依次尝试每个catch子句。在本例中,如果异常类型是FileNotFoundException,则第一个子句将被执行;如果异常类型是IOException,则第二个子句将被执行;如果异常既不是FileNotFoundException也不是IOException,则try-catch将会终止,并将异常向上继续传播。

示例7.11 Scala中的try-catch子句

注意

你会注意到一个Scala与Java的区别,Scala并不要求捕获受检异常checked exception)或在throws子句里声明。可以使用@throws注解声明一个throws子句,但这并不是必需的。关于@throws注解的详情,请参考9.2节。

finally子句

可以将那些无论是否抛出异常都需要执行的代码以表达式的形式包含在finally子句中。例如,你可能想要确保某个打开的文件被正确关闭,即使某个方法因为抛出了异常而退出。示例7.12给出了这样的例子:[5]

示例7.12 Scala中的try-finally子句

注意

示例7.12展示了确保非内存资源被正确关闭的惯用做法。这些资源可以是文件、套接字、数据库连接等。首先获取资源,然后在try代码块中使用资源,最后在finally代码块中关闭资源。关于这个习惯,Scala和Java是一致的。Scala提供了另一种技巧,即贷出模式loan pattern),可以更精简地达到相同的目的。我们将在9.4节详细介绍贷出模式。

交出值

与Scala的大多数其他控制结构一样,try-catch-finally最终返回一个值。例如,示例7.13展示了如何实现解析URL,但当URL格式有问题时返回一个默认的值。如果没有异常抛出,整个表达式的结果就是try子句的结果;如果有异常抛出且被捕获,整个表达式的结果就是对应的catch子句的结果;而如果有异常抛出但没有被捕获,整个表达式就没有结果。如果有finally子句,则该子句计算出来的值会被丢弃。finally子句一般都用于执行清理工作,如关闭文件。通常来说,它不应该改变主代码体或catch子句中计算出来的值。

示例7.13 交出值的catch语句

如果你熟悉Java,则需要注意的是,Scala的行为与Java的行为不同,仅仅是因为Java的try-finally子句并不返回某个值。与Java一样,当finally子句包含一个显式的返回语句,或者抛出某个异常时,这个返回值或异常将会“改写”(overrule)任何在之前的try代码块或某个catch子句中产生的值。例如,在下面这个函数定义中:

调用f()将得到2。相反,如果是如下代码:

调用g()将得到1。这两个函数的行为都很可能让多数程序员感到意外,因此,最好避免在finally子句中返回值,最好将finally子句用来确保某些副作用发生,如关闭一个打开的文件。