![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=1739283945-72BvXZhPQJSOVXTlNKkGIKFWPYd7nF8B-0-ec8c5e44865a3e4881df250c6f38c4ee)
不过假如你想要实现那种更像是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=1739283945-PagTSeJRzli1wfYIMuLpZfgokVvmINXS-0-6bd2c8cd40c063a1dc420eb77c470a6c)
这个定义没有问题,不过用起来有些别扭:
![](https://epubservercos.yuewen.com/E8DF3B/20205397808551606/epubprivate/OEBPS/Images/40272-00-209-3.jpg?sign=1739283945-y55bbpuWmWDLK9v4fRJXEtDL6AiNDVnT-0-1266cc479b3196494d75dc069ba284bf)
你大概更希望能不在函数字面量里写空的圆括号和=>符号,而是直接这样写:
![](https://epubservercos.yuewen.com/E8DF3B/20205397808551606/epubprivate/OEBPS/Images/40272-00-210-1.jpg?sign=1739283945-wO9njUJ2ecCXXaNQ8cNcaFHqtLaItWKC-0-d845d10855123ee3e1d96ae507667c83)
传名参数就是为此而生的。要让参数成为传名参数,需要给参数一个以=>开头的类型声明,而不是() =>。例如,可以像这样将myAssert的predicate参数转成传名参数:把类型“() => Boolean”改成“=> Boolean”。示例9.5给出了具体的样子:
![](https://epubservercos.yuewen.com/E8DF3B/20205397808551606/epubprivate/OEBPS/Images/40272-00-210-2.jpg?sign=1739283945-Saqhl63b8nwPybF7m5PKl5cBkQTLbyAl-0-f26b4ea19a444a309b5b782ce55e3cce)
示例9.5 使用传名参数
现在已经可以对要做断言的属性去掉空的参数列表了。这样做的结果就是byNameAssert用起来跟使用内建的控制结构完全一样:
![](https://epubservercos.yuewen.com/E8DF3B/20205397808551606/epubprivate/OEBPS/Images/40272-00-210-3.jpg?sign=1739283945-3vukHWq2TyFyDmVoF8FjX4tursjyZ3cg-0-91ee77c787c42bdd083b54a89acccfe8)
对传名(by-name)类型而言,空的参数列表,即(),是去掉的,这样的类型只能用于参数声明,并不存在传名变量或传名字段。
你可能会好奇为什么不能简单地用老旧的Boolean来作为其参数的类型声明,就像这样:
![](https://epubservercos.yuewen.com/E8DF3B/20205397808551606/epubprivate/OEBPS/Images/40272-00-210-4.jpg?sign=1739283945-Qw1x60ODF7z0Zvr84DpFvdFlv89cRdXx-0-9346b314029cb32407c5d25cce1f63b9)
这种组织方式当然也是合法的,boolAssert用起来也跟之前看上去完全一样:
![](https://epubservercos.yuewen.com/E8DF3B/20205397808551606/epubprivate/OEBPS/Images/40272-00-210-5.jpg?sign=1739283945-37Z14lZCyBPE3MKSCEjiIyGeQ7nQcLJX-0-efc4b43d979e4436ad46a5afa2356119)
不过,这两种方式有一个显著的区别需要注意。由于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=1739283945-WQF2wOazAHl3ECLtZu8HLoFj95UW689h-0-c84aba443693039c6143d3c7d20318f8)
而对同样的代码用byNameAssert来做断言的话,不会有异常抛出:
![](https://epubservercos.yuewen.com/E8DF3B/20205397808551606/epubprivate/OEBPS/Images/40272-00-211-2.jpg?sign=1739283945-OkSXHIfnkYIhgHOJPL0AakslAxZPKUu4-0-84379267b708f6b175cd374244c02888)