本文參考了:https://blog.rockthejvm.com/givens-vs-implicits/ ,同時增加筆者的一些例子和觀點(diǎn)。
這篇文章主要探討 Scala3 的轉(zhuǎn)換 與 Scala2 的隱式轉(zhuǎn)換的相似與區(qū)別之處,適用于對 Scala2 有一定熟悉的程序員,如果你直接開始學(xué)習(xí) Scala3,則可忽略本文。
1.Scala2 隱式 (Implicits) 特性
Implicits 特性是 Scala 最引人稱道和強(qiáng)大的特性,使用 Scala 精髓之一就是 隱式轉(zhuǎn)換。它允許在傳統(tǒng)的面相對象的代碼層次之外進(jìn)行代碼的抽象與增強(qiáng)。
如我們使用 Java 的某個類庫中的 api,如 java.sql.Connection,它的方法定義和成員變量寫死在了那里。但是使用 Scala 則可以通過提供隱式轉(zhuǎn)換的特性,直接對
Connection 提供新的方法,而用戶在使用時,不需要關(guān)心這個新的方法在哪里定義的,Scala 支持我們在 IDE 編寫代碼時,直接提示出這個新的方法。下面是一個例子:
import java.sql.Connection
import com.maple.scala3.implicts.sql.given_Conversion_Connection_RichConnection
given Conversion[Connection,RichConnection] with
override def apply(x: Connection): RichConnection = RichConnection(x)
class RichConnection(conn: Connection) {
//定義的新方法 executeUpdate,對數(shù)據(jù)操作
def executeUpdate(sql: String): Int = {
conn.prepareStatement(sql).executeUpdate()
}
}
def richConnection(conn: Connection): Unit =
conn.executeUpdate("update name from users set name = ")
如上述代碼,在 richConnection 中,看起來 Connection 新增了一個方法 executeUpdate,而實(shí)則是 Scala 通過隱式轉(zhuǎn)換將 Connection 轉(zhuǎn)換成了 RichConnection。
Scala Implicits 的使用場景:
- Implicits 是在 Scala 中創(chuàng)建 TypeClass 的重要工具。
- 使用 Implicits 可以擴(kuò)展現(xiàn)有類型的功能,例如上文中的 Connection 擴(kuò)展。
- Implicits 支持自動創(chuàng)建新類型并在編譯時將它們之間的類型關(guān)系聯(lián)系起來。
在 Scala 2中,上述功能均可以通過關(guān)鍵字 implicits 實(shí)現(xiàn),這包括 隱式變量 ( implicit val a = xxx )、隱式函數(shù)( implicit def xxx )、隱式類等( implicit class xxx() )。
但是這一套方案存在其缺點(diǎn),并且導(dǎo)致了其在使用起來比較復(fù)雜和難懂,代碼風(fēng)格也得不到一定的統(tǒng)一。
1.如果我們的代碼中使用到了不少 implicits 代碼,我們很難 顯示的去尋找這些隱式轉(zhuǎn)換到底在進(jìn)行轉(zhuǎn)換的。因?yàn)?implicit 可以隨處定義,并且在 import 時,可以粗略的 import,即使用類似
import com.maple.implicts._的形式直接把該包下面所有的信息都引入進(jìn)來了。2.當(dāng)我們寫的方法上需要一個隱式參數(shù)時,我們需要 import 其對應(yīng)的隱式轉(zhuǎn)換定義,來讓這段代碼能夠編譯通過。IDE 不會自定完成這個導(dǎo)入過程,我們需要自己去尋早這些隱式轉(zhuǎn)換類的定義包。舉個例子,有可能一個 String => Person 的轉(zhuǎn)換在多處都有定義,而這些定義有略有不同,IDE怎么去進(jìn)行 import 呢?所以我們得自己去尋找和導(dǎo)入,是不是感覺有些麻煩,對初學(xué)者和新的代碼維護(hù)者來說,感覺會比較沮喪。
3.對于一個隱式方法,其參數(shù)上如果有 implicit 參數(shù)時,這個方法可以層層被使用而且被進(jìn)行轉(zhuǎn)換。這些轉(zhuǎn)換可能會存在潛在的錯誤,而我們平時用的最多的就是這種方式,這就使得它變得更加危險(xiǎn)了。舉個例子:TODO
-
- 其他的一些讓人煩惱的點(diǎn):
- 隱式變量等總是需要去命名,但是其實(shí)我們重來沒用用到這個命名,如下面例子 personToString 從來沒有用到過。
implicit val personToString = Person().toString()
如果一個函數(shù)入?yún)?shù)包含了 implicit 參數(shù),則在傳遞的過程中,會讓人迷惑,如下面這段代碼,它的隱式參數(shù)可以一層一層向外拋,直到最外層需要用時,在上下文找到一個隱式定義變量,則編譯通過。
//第一層
def getName(implict name:String) = name
//第二層
def getNames(implict nickName:String) = getName
//第三層
implict val defaultName = "maple"
def getNameList(name:List[String]) =
if(name.isEmpty) getNames()
else xxx
2. Scala3 Implicit 的全面重構(gòu)
隱式轉(zhuǎn)換( Implicit conversions ) 現(xiàn)在需要被顯示的指定,這讓開發(fā)者們?nèi)玑屩刎?fù),類型的定于與轉(zhuǎn)換在 3 中將變得異常的清晰和好用。
在 Scala 3 中,擴(kuò)展方法 ( extension method ) 的概念是獨(dú)立的,因此我們可以單獨(dú)實(shí)現(xiàn)需要需要 implict 而不需要轉(zhuǎn)換( conversion )的模式。
這樣一來,conversions 的場景可能會大量減少,因?yàn)檗D(zhuǎn)換是隱秘的,顯示的去 import 和 使用 轉(zhuǎn)換可能更有利于代碼的維護(hù)和可讀性。
現(xiàn)在,在 scala 3 中,定一個一個轉(zhuǎn)換的語法如下:
pacage com.mapla.scala3.implicts
case class Person(name: String):
def greet: String = s"Hey, I'm $name. Scala rocks!"
given [named_name : ] Conversion[String,Person] with
overide def apply(original: String): Person = Person(original)
- name 是可以省略掉的,這樣編譯器會通過一定的規(guī)律自動給我們生成一個name。
- 在使用該轉(zhuǎn)換函數(shù)時,必須顯示且精確的引用出來,下面是例子。
import com.maple.scala3.implicts.given_Conversion_String_Person
// import com.maple.scala3.implicts._ 編譯錯誤
def testStringToPerson(): Unit =
println("Jeff H. Ray".greet)
- 如上面代碼,必須精確的引用到該轉(zhuǎn)換,該名字是編譯器自動生成。
未完待續(xù)?。?!