7.6 没有break和continue的日子
你可能已经注意到了,我们并没有提到break或continue。Scala去掉了这两个命令,因为它们与接下来一章会讲到的函数字面量不搭。在while循环中,continue的含义是清楚的,不过在函数字面量中应该是什么含义才合理呢?虽然Scala同时支持指令式和函数式的编程风格,但是在这个具体的问题上,它更倾向于函数式编程风格,以换取语言的简单。不过别担心,就算没有了break和continue,还有很多其他方式可以用来编程。而且,如果你用好了函数字面量,则使用这里提到的其他方式通常比原来的代码更短。
最简单的方式是用if换掉每个continue,用布尔值换掉每个break。布尔值表示包含它的while循环是否继续。例如,假设你要检索参数列表,找一个以“.scala”结尾但不以连字符开头的字符串,那么用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.10节将会对尾递归优化做更详细的讨论。