![Scala编程(第4版)](https://wfqqreader-1252317822.image.myqcloud.com/cover/353/38381353/b_38381353.jpg)
7.4 用try表达式实现异常处理
Scala的异常处理跟其他语言类似。方法除了正常地返回某个值,也可以通过抛出异常终止执行。方法的调用方要么捕获并处理这个异常,要么自我终止,让异常传播到更上层调用方。异常通过这种方式传播,逐个展开调用栈,直到某个方法处理该异常或者再没有更多方法了为止。
抛出异常
在Scala中抛出异常跟Java看上去一样。你需要创建一个异常对象然后用throw关键字将它抛出:
![](https://epubservercos.yuewen.com/E8DF3B/20205397808551606/epubprivate/OEBPS/Images/40272-00-159-1.jpg?sign=1739140731-f75z49g01UCkTUsAGSdmk07bKq8dHDtB-0-7f3630a1a03830a7ace2b97ebfeb9a06)
虽然看上去有些自相矛盾,在Scala中throw是一个有结果类型的表达式。如下是一个带有结果类型的示例:
![](https://epubservercos.yuewen.com/E8DF3B/20205397808551606/epubprivate/OEBPS/Images/40272-00-159-2.jpg?sign=1739140731-rpfM2NNtvOhkMJSg6yqmRvUI9RvG5jo3-0-b6ac2872295ea6462dee0e87698f8682)
在这段代码中,如果n是偶数,half将被初始化成n的一半。如果n不是偶数,那么在half被初始化之前,就会有异常被抛出。因此,我们可以安全地将抛出异常当作任何类型的值来对待。任何想要使用throw给出的这个返回值的上下文都没有机会真正使用它,也就不必担心有其他问题。
从技术上讲,抛出异常这个表达式的类型是Nothing。哪怕表达式从不实际被求值,也可以用throw。这个技术细节听上去有点奇怪,不过在前一例这样的场景下,还是很常见也很有用的。if的一个分支计算出某个值,而另一个分支抛出异常并计算出Nothing。整个if表达式的类型就是那个计算出某个值的分支的类型。我们将在11.3节对Nothing做进一步的介绍。
捕获异常
可以用示例7.11中的语法来捕获异常。catch子句的语法之所以是这样,为的是与Scala的一个重要组成部分,模式匹配(pattern matching),保持一致。我们将在本章简单介绍并在第15章详细介绍模式匹配这个强大的功能。
![](https://epubservercos.yuewen.com/E8DF3B/20205397808551606/epubprivate/OEBPS/Images/40272-00-160-1.jpg?sign=1739140731-CMeQUw11E9L1jECi2ajpLamzeMHUzA3o-0-f7f322e730f3c25ce43ea2a4054d09a3)
示例7.11 Scala中的try-catch子句
这个try-catch表达式跟其他带有异常处理的语言一样。首先代码体会被执行,如果抛出异常,则会依次尝试每个catch子句。在本例中,如果异常的类型是FileNotFoundException,第一个子句将被执行。如果异常类型是IOException,那么第二个子句将被执行。而如果异常既不是FileNotFoundException也不是IOException,try-catch将会终止,异常将向上继续传播。
注意
你会注意到一个Scala跟Java的区别,Scala并不要求你捕获受检异常(checked exception)或在throws子句里声明。可以选择用@throws注解来声明一个throws子句,但这并不是必需的。关于@throws的详情,请参考31.2节。
finally子句
可以将那些不论是否抛出异常都想执行的代码以表达式的形式包在finally子句里。例如,你可能想要确保某个打开的文件要被正确关闭,哪怕某个方法因为抛出了异常而退出。示例7.12给出了这样的例子:[5]
![](https://epubservercos.yuewen.com/E8DF3B/20205397808551606/epubprivate/OEBPS/Images/40272-00-161-1.jpg?sign=1739140731-D7aHNdj955oJIEUwH9Zbu2NIUdFCI3gW-0-1db564c0a615c7de4289aee92ff73790)
示例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子句中计算出来的值。
![](https://epubservercos.yuewen.com/E8DF3B/20205397808551606/epubprivate/OEBPS/Images/40272-00-162-2.jpg?sign=1739140731-VhTXYV24GRwan2GWTo7X8Kw4PhJYC0az-0-3bd52a708a6664eaeb9f0fcad00ddc0f)
示例7.13 交出值的catch语句
如果你熟悉Java,需要注意的是Scala的行为跟Java不同,仅仅是因为Java的try-finally并不返回某个值。跟Java一样,当finally子句包含一个显式的返回语句,或者抛出某个异常,那么这个返回值或异常将会“改写”(overrule)任何在之前的try代码块或某个catch子句中产生的值。例如,在下面这个刻意做成这样的函数定义中:
![](https://epubservercos.yuewen.com/E8DF3B/20205397808551606/epubprivate/OEBPS/Images/40272-00-162-1.jpg?sign=1739140731-a3NuyQde8dWADwLACIUWszsQ3xMZmU4d-0-894cc727842eb8227fb8edfdf5c5a98f)
调用f()将得到2。相反,如果是如下代码:
![](https://epubservercos.yuewen.com/E8DF3B/20205397808551606/epubprivate/OEBPS/Images/40272-00-162-3.jpg?sign=1739140731-YpjQ1md9WLAyJKf1lUdhmclhmkc12dfI-0-2bc5fc78d1531114fde6b9db44e7fdd4)
调用g()将得到1。这两个函数的行为都很可能让多数程序员感到意外。因此,最好避免在finally子句中返回值,最好将finally子句用来确保某些副作用发生,比如关闭一个打开的文件。