![Scala编程(第4版)](https://wfqqreader-1252317822.image.myqcloud.com/cover/353/38381353/b_38381353.jpg)
9.5 传名参数
前一节的withPrintWriter方法跟语言内建的控制结构(比如if和while)不同,花括号中间的代码接收一个入参。传入withPrintWriter的函数需要一个类型为PrintWriter的入参,这个入参就是下面代码当中的“writer =>”:
![](https://epubservercos.yuewen.com/E8DF3B/20205397808551606/epubprivate/OEBPS/Images/40272-00-209-1.jpg?sign=1739692471-CB8XbYJg35yuBAWSWB8gO43OsLml1Kdv-0-a8f3c39acce4972f7e2cf5e19a609166)
不过假如你想要实现那种更像是if或while的控制结构,没有值需要传入花括号中间的代码,该怎么办呢?为了帮助我们应对这样的场景,Scala提供了传名参数(by-name parameter)。
我们来看一个具体的例子,假定你想要实现一个名为myAssert的断言结构。[3]这个myAssert函数将接收一个函数值作为输入,然后通过一个标记来决定如何处理。如果标记位打开,myAssert将调用传入的函数,验证这个函数返回了true。而如果标记位关闭,那么myAssert将什么也不做。
如果不使用传名参数,你可能会这样来实现myAssert:
![](https://epubservercos.yuewen.com/E8DF3B/20205397808551606/epubprivate/OEBPS/Images/40272-00-209-2.jpg?sign=1739692471-HFMCwTvnfTEIlTO2FiKXMvzsg2s33Inb-0-c694dfb64ab2d0ed46c61d1c57d3d63c)
这个定义没有问题,不过用起来有些别扭:
![](https://epubservercos.yuewen.com/E8DF3B/20205397808551606/epubprivate/OEBPS/Images/40272-00-209-3.jpg?sign=1739692471-FCrnFEbF8I2PLkLC0lFJ2xseV6ETbFu6-0-9f9b74d9d2b3fc2a00d1ceb59d0d9cf1)
你大概更希望能不在函数字面量里写空的圆括号和=>符号,而是直接这样写:
![](https://epubservercos.yuewen.com/E8DF3B/20205397808551606/epubprivate/OEBPS/Images/40272-00-210-1.jpg?sign=1739692471-MrnUKHOFyiqxLXpXWYpsTeqKnyWEEwa7-0-f340d717ecb3afe0d0d3df4544311997)
传名参数就是为此而生的。要让参数成为传名参数,需要给参数一个以=>开头的类型声明,而不是() =>。例如,可以像这样将myAssert的predicate参数转成传名参数:把类型“() => Boolean”改成“=> Boolean”。示例9.5给出了具体的样子:
![](https://epubservercos.yuewen.com/E8DF3B/20205397808551606/epubprivate/OEBPS/Images/40272-00-210-2.jpg?sign=1739692471-4Bwhl8ldjiURR5GzkFi8j9iAbVFSQN0R-0-4db258a5c7b6b5a90429a26107f8d643)
示例9.5 使用传名参数
现在已经可以对要做断言的属性去掉空的参数列表了。这样做的结果就是byNameAssert用起来跟使用内建的控制结构完全一样:
![](https://epubservercos.yuewen.com/E8DF3B/20205397808551606/epubprivate/OEBPS/Images/40272-00-210-3.jpg?sign=1739692471-NScPcmgKZEtyFglzA2b9pwhg48g1hDB0-0-5a459d05c4599397acb3d09f18b766a0)
对传名(by-name)类型而言,空的参数列表,即(),是去掉的,这样的类型只能用于参数声明,并不存在传名变量或传名字段。
你可能会好奇为什么不能简单地用老旧的Boolean来作为其参数的类型声明,就像这样:
![](https://epubservercos.yuewen.com/E8DF3B/20205397808551606/epubprivate/OEBPS/Images/40272-00-210-4.jpg?sign=1739692471-q6Pcf27J25xWcq84aRNwZGT3Ycl0EjuK-0-d0b8db3ff49ca984a60ba8c7ecec73fa)
这种组织方式当然也是合法的,boolAssert用起来也跟之前看上去完全一样:
![](https://epubservercos.yuewen.com/E8DF3B/20205397808551606/epubprivate/OEBPS/Images/40272-00-210-5.jpg?sign=1739692471-lcTU7M62ktvyGbP0owTWwQ2QTmeBNzfm-0-ba7978e53dc67c3c6778ee7693b2fdb9)
不过,这两种方式有一个显著的区别需要注意。由于boolAssert的参数类型为Boolean,在boolAssert(5 > 3)圆括号中的表达式将“先于”对boolAssert的调用被求值。而由于byNameAssert的predicate参数类型是=> Boolean,在byNameAssert(5 > 3)的圆括号中的表达式在调用byNameAssert之前并不会被求值,而是会有一个函数值被创建出来,这个函数值的apply方法将会对5 > 3求值,传入byNameAssert的是这个函数值。
因此,两种方式的区别在于如果断言被禁用,你将能够观察到boolAssert的圆括号当中的表达式的副作用,而用byNameAssert则不会。例如,如果断言被禁用,那么我们断言“x / 0 == 0”的话,boolAssert会抛异常:
![](https://epubservercos.yuewen.com/E8DF3B/20205397808551606/epubprivate/OEBPS/Images/40272-00-211-1.jpg?sign=1739692471-cITom3YQj8oWOONXkonuto0p7Gc23n96-0-8926e29dfcdcffbb9080c5a87e930885)
而对同样的代码用byNameAssert来做断言的话,不会有异常抛出:
![](https://epubservercos.yuewen.com/E8DF3B/20205397808551606/epubprivate/OEBPS/Images/40272-00-211-2.jpg?sign=1739692471-m3zV4WkCYcTSjkbZuxwhDODPwUhUCrpH-0-b1cf0f672db34dc387cccf91608fe48a)