鍍金池/ 教程/ Scala/ 傳名參數
包對象
Ordered Trait
組合和繼承–定義 final 成員
基本數據類型
Match 表達式
類和對象 (三)
操作基本數據類型
for 表達式
組合和繼承–重載成員函數和方法
類和對象 (二)
組合和繼承–定義 factory 對象
組合和繼承–多態(tài)和動態(tài)綁定
Trait 的基本概念
if 表達式
組合和繼承–抽象類
函數–函數字面量的一些簡化寫法
while 循環(huán)
組合和繼承–使用組合還是繼承?
訪問控制修飾符
Trait 示例–Rectangular 對象
組合和繼承–定義參數化成員變量
組合和繼承–定義無參數方法
類和對象 (一)
函數–閉包
函數–類成員函數
Scala 基本數據類型的實現方法
try 表達式處理異常
選擇瘦接口還是胖接口設計?
組合和繼承–小結
創(chuàng)建新的控制結構
使用 import
為訪問控制修飾符添加作用域
Scala 的類層次關系
類和對象 (五)
傳名參數
柯里化函數
函數–頭等公民
組合和組合和繼承–定義 heighten 和 widen 函數
使用 Package–將代碼放入包中
隱含的 import
所有類的公共子類–底層類型
進一步 Scala
函數–局部函數
引用包中的代碼
組合和繼承–使用 override 修飾符
組合和繼承–實現類 Element 的 above,beside 和 toString()方法
類和對象 (四)
函數–尾遞歸
沒有“break”和“continue”的日子
組合和繼承–調用基類構造函數
減低代碼重復
函數–函數–可變參數,命名參數,缺省參數
起步 Scala
組合和繼承–擴展類
函數–部分應用的函數
開始神奇的 Scala編程之旅
組合和繼承–概述
Trait 用來實現可疊加的修改操作

傳名參數

上篇我們使用柯里化函數定義一個控制機構 withPrintWriter,它使用時語法調用有如 Scala 內置的控制結構:

val file = new File("date.txt")
withPrintWriter(file){
  writer => writer.println(new java.util.Date)
}

不過仔細看一看這段代碼,它和 scala 內置的 if 或 while 表達式還是有些區(qū)別的,withPrintWrite r的{}中的函數是帶參數的含有“writer=>”。 如果你想讓它完全和 if 和 while 的語法一致,在 Scala 中可以使用傳民參數來解決這個問題。

:我們知道通常函數參數傳遞的兩種模式,一是傳值,一是引用。而這里是第三種按名稱傳遞。

下面我們以一個具體的例子來說明傳名參數的用法:

var assertionsEnabled=true
def myAssert(predicate: () => Boolean ) =
  if(assertionsEnabled && !predicate())
    throw new AssertionError

這個 myAssert 函數的參數為一個函數類型,如果標志 assertionsEnabled 為 True 時,mymyAssert 根據 predicate 的真假決定是否拋出異常,如果 assertionsEnabled 為 false,則這個函數什么也不做。

這個定義沒什么問題,但調用起來看起來卻有些別扭,比如:

myAssert(() => 5 >3 )

還需要 ()=>,你可以希望直接使用 5>3,但此時會報錯:

scala> myAssert(5 >3 )
<console>:10: error: type mismatch;
 found   : Boolean(true)
 required: () => Boolean
              myAssert(5 >3 )

此時,我們可以把按值傳遞(上面使用的是按值傳遞,傳遞的是函數類型的值)參數修改為按名稱傳遞的參數,修改方法,是使用=>開始而不是 ()=>來定義函數類型,如下:

def myNameAssert(predicate:  => Boolean ) =
  if(assertionsEnabled && !predicate)
    throw new AssertionError

此時你就可以直接使用下面的語法來調用 myNameAssert:

myNameAssert(5>3)

此時就和 Scala 內置控制結構一樣了,看到這里,你可能會想我為什么不直接把參數類型定義為 Boolean,比如:

def boolAssert(predicate: Boolean ) =
  if(assertionsEnabled && !predicate)
    throw new AssertionError

調用也可以使用

boolAssert(5>3)

和 myNameAssert 調用看起來也沒什么區(qū)別,其實兩者有著本質的區(qū)別,一個是傳值參數,一個是傳名參數,在調用 boolAssert(5>3)時,5>3 是已經計算出為 true,然后傳遞給 boolAssert 方法,而 myNameAssert(5>3),表達式 5>3 沒有事先計算好傳遞給 myNameAssert,而是先創(chuàng)建一個函數類型的參數值,這個函數的 apply 方法將計算5>3,然后這個函數類型的值作為參數傳給 myNameAssert。

因此這兩個函數一個明顯的區(qū)別是,如果設置 assertionsEnabled 為 false,然后試圖計算 x/0 ==0,

scala> assertionsEnabled=false
assertionsEnabled: Boolean = false
scala> val x = 5
x: Int = 5
scala> boolAssert ( x /0 ==0)
java.lang.ArithmeticException: / by zero
  ... 32 elided
scala> myNameAssert ( x / 0 ==0)

可以看到 boolAssert 拋出 java.lang.ArithmeticException: / by zero 異常,這是因為這是個傳值參數,首先計算 x /0 ,而拋出異常,而 myNameAssert 沒有任何顯示,這是因為這是個傳名參數,傳入的是一個函數類型的值,不會先計算 x /0 ==0,而在 myNameAssert 函數體內,由于 assertionsEnabled 為 false,傳入的 predicate 沒有必要計算(短路計算),因此什么也不會打印。如果我們把 myNameAssert 修改下,把 predicate 放在前面:

scala> def myNameAssert1(predicate:  => Boolean ) =
     |   if( !predicate && assertionsEnabled )
     |     throw new AssertionError
myNameAssert1: (predicate: => Boolean)Unit
scala> myNameAssert1 ( x/0 ==0)
java.lang.ArithmeticException: / by zero
  at $anonfun$1.apply$mcZ$sp(<console>:11)
  at .myNameAssert1(<console>:9)
  ... 32 elided

這個傳名參數函數也拋出異常(你可以想想是為什么?)

前面的 withPrintWriter 我們暫時沒法使用傳名參數,去掉 writer=>,否則就難以實現“租賃模式”,不過我們可以看看下面的例子,設計一個 withHelloWorld 控制結構,這個 withHelloWorld 總打印一個“hello,world”

import scala.io._
import java.io._
def withHelloWorld ( op: => Unit) {
  op   
  println("Hello,world")
}
val file = new File("date.txt")
withHelloWorld{
  val writer=new PrintWriter(file)
  try{
   writer.println(new java.util.Date)
  }finally{
    writer.close()
  }
}
withHelloWorld {
  println ("Hello,Guidebee")
} 

Hello,world 
Hello,Guidebee
Hello,world

可以看到 withHelloWorld 的調用語法和 Scala 內置控制結構非常象了。