7.6 没有break和continue的日子
你可能已经注意到了,我们并没有提到break或continue。Scala去掉了这两个命令,因为它们跟接下来一章会讲到的函数字面量不搭。在while循环中,continue的含义是清楚的,不过在函数字面量当中应该是什么含义才合理呢?尽管Scala同时支持指令式和函数式风格的编程,在这个具体的问题上,它更倾向于函数式编程,以换取语言的简单。不过别担心,就算没有了break和continue,一样有很多其他方式来编程。而且,如果你用好了函数字面量,这里提到的其他方式通常比原来的代码更短。
最简单的方式是用if换掉每个continue,用布尔值换掉每个break。布尔值表示包含它的while循环是否继续。例如,假定你要检索参数列表,找一个以“.scala”结尾但不以连字符(hyphen)开头的字符串。用Java的话你可能会这样写(如果你喜欢while循环、break和continue):
如果要将这段Java代码直接翻译成Scala,可以把先if再continue的写法改成用if将整个while循环体剩余的部分包起来。为了去掉break,通常会添加一个布尔值的变量,表示是否要继续循环,不过在本例中可以直接复用foundIt。通过上述两种技巧,代码看上去如示例7.16所示:
示例7.16 不使用break或continue的循环
示例7.16中的Scala代码跟原本的Java代码很相似。所有基础的组件都在,顺序也相同。有两个可被重新赋值的变量和一个while循环,而在循环中有一个对i是否小于args.length的检查、一个对"-"的检查,和一个对".scala"的检查。
如果你想去掉示例7.16中的var,一种做法是将循环重写为递归的函数。比方说,可以定义一个searchFrom函数,接收一个整数作为输入,从那里开始向前检索,然后返回找到的入参下标。通过这个技巧,代码看上去如示例7.17所示:
示例7.17 用于替代var循环的递归
示例7.17的这个版本采用了对人来说有意义的函数名,并且使用递归替换掉了循环。每一个continue都替换成一次以i + 1作为入参的递归调用,从效果上讲跳到了下一个整数值。一旦习惯了递归,不少人都会认为这种风格的编程方式更易于理解。
注意
Scala编译器实际上并不会对示例7.17中的代码生成递归的函数。由于所有的递归调用都发生在函数尾部(tail-call position),编译器会生成与while循环类似的代码。每一次递归都会被实现成跳回到函数开始的位置。8.9节将会对尾递归优化做更详细的讨论。
如果经过这些讨论你仍觉得需要使用break,Scala标准类库也提供了帮助。scala.util.control包的Break类给出了一个break方法,可以被用来退出包含它的用breakable标记的代码块。如下是使用这个由类库提供的break方法的示例:
这段代码将不断反复地从标准输入读取非空的文本行。而一旦用户输入空行,控制流就会从外层的breakable代码块退出,while循环也随之退出。
Break类实现break的方式是抛出一个异常,然后由外围的对breakable方法的应用所捕获。因此,对break的调用并不需要跟对breakable的调用放在同一个方法内。