1. 隱式規則
隱式定義指的是哪些我們允許編譯器插入程序以解決類型錯誤的定義。
隱式轉換受如下規則約束:
- 標記規則:只有標記爲implicit的定義纔可用。
- 作用域規則:被插入的隱式轉換必須是當前作用域的單個標識符,或者跟隱式轉換的源類型或目標類型有關聯。
- 每次一個規則:每次只能有一個隱式定義被插入。
- 顯示優先原則:只要代碼按編寫的樣子能夠通過類型檢查,就不嘗試隱式定義。
哪些地方會嘗試隱式轉換:
- 轉換到一個預期的類型
- 對某個(成員)選擇接受端(即字段、方法調用等)的轉換
- 隱式參數
2. 隱式轉換到一個預期的類型
每當編譯器看見一個X而它需要一個Y的時候,它就會查找一個能將X轉換成Y的隱式轉換。
如:雙精度浮點數不能轉換爲整數,會丟失精度
但可以定義一個隱式轉換讓它能夠走下去:
在定義隱式轉換之前需要導入包,博主未導入會報錯:
import scala.language.implicitConversions
定義隱式轉換:
再次運行代碼:
3. 轉換接收端
接受端是指方法被調用的那個對象。
-
與新類型互操作
假設有一個新的類,定義的 ** 方法,作用是求冪:
類定義:
沒有隱式轉換之前:
報錯原因是因爲1 是Int類型,而Int類型沒有 ** 方法
定義隱式轉換:
-
模擬新的語法
例如,Map的語法:
或許你會認爲 -> 是Scala的語法特性,其實這是在scala.Predef對象裏ArrowAssoc的一個隱式轉換,如源代碼:
-
隱式類
如上圖,隱式類的格式爲: implicit class xxx
隱式類有如下要求:
- 不能是樣例類
- 構造方法必須有且僅有一個參數
- 隱式類必須存在於另一個對象、類或特質裏面
4. 隱式參數
示例,用戶命令提示字符串:
def greet 使用了柯里化,第二個參數開頭使用了implicit,prompt和drink都是隱式參數
class PreferredPrompt(val preference: String)
class PreferredDrink(val preference: String)
object Greeter {
def greet(name: String)(implicit prompt: PreferredPrompt, drink: PreferredDrink) = {
println("Welcom, " + name + ". The system is ready.")
println("enjoy a cup of " + drink.preference + "?")
println(prompt.preference)
}
}
object My {
implicit val prompt = new PreferredPrompt("Yes, master> ")
implicit val drink = new PreferredDrink("tea")
}
運行,當你執行上面代碼後,在命令行執行如下命令:
因爲現在的作用域裏沒有可用的參數,需要進行導入
導入,並再次運行:
當然,也可以顯示的給出參數:
另一個例子
def maxListOrdering[T](elements: List[T])
(ordering: Ordering[T]): T =
elements match {
case List() => throw new IllegalArgumentException("empty list!")
case List(x) => x
case x :: rest => {
val maxRest = maxListOrdering(rest)(ordering)
if (ordering.gt(x, maxRest)) x
else maxRest
}
}
函數的作用是按照Ordering[T]的比較規則來尋找elements列表的最值。
maxListOrdering接收一個List[T]作爲入參,以及一個類型爲Ordering[T]的入參。第二個入參是爲了指定比較的順序,這樣,某些沒有內建的順序的類型也可以進行比較。
運行示例:
你可以看到,用起來很麻煩,必須給出一個顯示的排序。
接下來江第二個參數標記爲隱式的:
def maxListImpParm[T](elements: List[T])
(implicit ordering: Ordering[T]): T =
elements match {
case List() => throw new IllegalArgumentException("empty list!")
case List(x) => x
case x :: rest => {
val maxRest = maxListImpParm(rest)(ordering)
if (ordering.gt(x, maxRest)) x
else maxRest
}
}
其中ordering參數用來表述T的排序規則。具體因爲是因爲,Scala標準類庫對許多常見的類型都提供了隱式的“排序”方法,所以這裏可以直接來使用,如:
5. 上下文界定
在上面的代碼種,你會發現有兩處地方我們使用了第二個參數ordering:
那我們如和去掉對ordering的使用呢?
- 對於第一次使用,因爲maxListImpParm本身第二個參數就是隱式參數,所以可以省略。
- 第二個參數,可以根據標準類庫種定義瞭如下方法:
def implicitly[T](implicit t: T) = t
對比,(implicit ordering: Ordering[T]),所以我們只需要如下調用即可:
(implicitly[Ordering[T]].gt(x, maxRest)) x
所以,這個版本的代碼爲:
所以,這個版本的函數的方法體中,我們沒有使用到第二個參數ordering,因此,這個參數名可以隨便更改,而不用更改方法體的內容。
因爲這樣的操作很常用,Scala允許省這個參數,並使用上下文界定來縮短方法簽名,如:
[T : Ordering]這樣的語法就是一個上下文界定,它做了兩件事:
- 像平常一樣引入了一個類型參數T
- 添加了一個類型爲Ordering[T]的隱式參數
6. 當有多個轉換可選時
如果有多個隱式轉換都符合某個場景,那它是如何選擇呢?
Scala-2.7以前,如果發生多個隱式轉換都符合的話,編譯器會拒絕從中選擇,即報錯。
Scala-2.8以後,對其有所放寬:如果可用的轉換當中有某個轉換嚴格來說比其他的更具體,那麼編譯器就會選擇這個轉換。
滿足如下條件時,就說某個隱式轉換比另一個更具體:
- 前者的入參類型時後者入參類型的子類型
- 兩者都是方法,而前者所在的類擴展自後者所在的類