隱式轉換是 Scala 的一項強大功能,讓使用者可以提供一個類型的參數,彷彿它是另一個類型,以避免樣板。
請注意,在 Scala 2 中,隱式轉換也用於提供封閉類別的其他成員(請參閱 隱式類別)。在 Scala 3 中,我們建議透過定義 延伸方法來解決此用例,而不是隱式轉換(儘管標準函式庫基於歷史原因仍依賴隱式轉換)。
範例
例如,考慮一個方法 findUserById
,它採用 Long
類型的參數
def findUserById(id: Long): Option[User]
為簡潔起見,我們省略類型 User
的定義,這對我們的範例並不重要。
在 Scala 中,可以呼叫方法 findUserById
,並使用類型為 Int
的引數,而不是預期的類型 Long
,因為引數會隱式轉換為類型 Long
val id: Int = 42
findUserById(id) // OK
此程式碼不會因「類型不符:預期 Long
,但找到 Int
」等錯誤而無法編譯,因為有一個隱式轉換,將引數 id
轉換為類型 Long
的值。
詳細說明
本節說明如何定義和使用隱式轉換。
定義隱式轉換
在 Scala 2 中,從類型 S
到類型 T
的隱式轉換是由 隱式類別 T
定義的,它採用單一建構函數參數類型 S
,一個函數類型 S => T
的 隱式值,或一個可轉換為該類型值的隱式方法。
例如,以下程式碼定義從 Int
到 Long
的隱式轉換
import scala.language.implicitConversions
implicit def int2long(x: Int): Long = x.toLong
這是一個可轉換為類型 Int => Long
值的隱式方法。
請參閱下方「當心隱式轉換的力量」一節,了解開頭的子句 import scala.language.implicitConversions
的說明。
在 Scala 3 中,從類型 S
到類型 T
的隱式轉換是由 given
執行個體定義的,類型為 scala.Conversion[S, T]
。為了與 Scala 2 相容,也可以透過隱式方法定義(在 Scala 2 標籤中閱讀更多資訊)。
例如,此程式碼定義從 Int
到 Long
的隱式轉換
given int2long: Conversion[Int, Long] with
def apply(x: Int): Long = x.toLong
與其他 given 定義一樣,隱式轉換可以是匿名的
given Conversion[Int, Long] with
def apply(x: Int): Long = x.toLong
使用別名,可以更簡潔地表達為
given Conversion[Int, Long] = (x: Int) => x.toLong
使用隱式轉換
隱式轉換應用於兩種情況
- 如果表達式
e
的類型為S
,且S
不符合表達式的預期類型T
。 - 在選擇
e.m
中,如果e
的類型為S
,且選擇器m
沒有表示S
的成員(支援 Scala-2 風格 擴充方法)。
在第一種情況下,尋找適用於 e
且其結果類型符合 T
的轉換 c
。
在上述範例中,當我們將類型為 Int
的引數 id
傳遞給方法 findUserById
時,會插入隱式轉換 int2long(id)
。
在第二種情況下,尋找適用於 e
且其結果包含名為 m
的成員的轉換 c
。
一個範例是比較兩個字串 "foo" < "bar"
。在這種情況下,String
沒有成員 <
,因此會插入隱式轉換 Predef.augmentString("foo") < "bar"
。(scala.Predef
會自動匯入所有 Scala 程式。)
如何將隱式轉換納入範圍?
當編譯器尋找適用的轉換時
- 首先,它會查看目前的詞彙範圍
- 在目前範圍或外部範圍中定義的隱式轉換
- 匯入的隱式轉換
- 由萬用字元匯入的隱式轉換(僅限 Scala 2)
- 然後,它會查看與引數類型
S
或預期類型T
關聯 的伴隨物件。與類型X
關聯的伴隨物件為- 伴隨物件
X
本身 - 與
X
的任何繼承類型關聯的伴隨物件 - 與
X
中的任何類型引數關聯的伴隨物件 - 如果
X
是內部類別,則它會嵌入其中外部物件
- 伴隨物件
例如,考慮在物件 Conversions
中定義的隱式轉換 fromStringToUser
import scala.language.implicitConversions
object Conversions {
implicit def fromStringToUser(name: String): User = (name: String) => User(name)
}
object Conversions:
given fromStringToUser: Conversion[String, User] = (name: String) => User(name)
以下匯入將等效地將轉換納入範圍
import Conversions.fromStringToUser
// or
import Conversions._
import Conversions.fromStringToUser
// or
import Conversions.given
// or
import Conversions.{given Conversion[String, User]}
請注意,在 Scala 3 中,萬用字元匯入 (即 import Conversions.*
) 不會匯入指定的定義。
在引言範例中,從 Int
到 Long
的轉換不需要匯入,因為它是在物件 Int
中定義,而該物件是類型 Int
的伴侶物件。
進一步閱讀:Scala 在哪裡尋找隱式內容? (在 Stackoverflow 上)。
小心隱式轉換的力量
由於隱式轉換在不加選擇地使用時可能會造成陷阱,因此編譯器在編譯隱式轉換定義時會發出警告。
若要關閉警告,請執行下列任一動作
- 將
scala.language.implicitConversions
匯入隱式轉換定義的範圍 - 使用
-language:implicitConversions
呼叫編譯器
當編譯器套用轉換時,不會發出警告。
由於隱式轉換在不加選擇地使用時可能會造成陷阱,因此編譯器在兩種情況下會發出警告
- 編譯 Scala 2 風格的隱式轉換定義時。
- 在呼叫位置,其中給定的
scala.Conversion
執行個體會插入為轉換。
若要關閉警告,請執行下列任一動作
- 將
scala.language.implicitConversions
匯入下列範圍- Scala 2 風格的隱式轉換定義
- 呼叫位置,其中給定的
scala.Conversion
執行個體會插入為轉換。
- 使用
-language:implicitConversions
呼叫編譯器