前言
我們都知道scala以簡潔著稱,怎麼簡單怎麼來。對於scala的簡潔語法,闊以說熟悉的人愛死scala,不熟悉的人被scala折磨死。對於scala這種語言,習慣了java的同學經常聽到一些新的名詞,不可思議而且神神祕祕,對,沒錯,說的就是你——隱式轉換,掖着藏着,快讓我窺探一下你的祕密。
常規問題:隱式轉換是什麼鬼
在這個地方,我們暫且先不談隱式轉換是什麼,先瞄準scala的類型轉換,對於scala的Byte,Short,Int和Long類型,如果我們Byte -> Short -> Int -> Long這樣轉換的話,那麼scala就自動幫我們實現了。除此之外,我們常見的還有多態語法中的類型轉換,比如 子類 -> 父類 -> 特質。這些都是scala在默認情況下就支持的自動類型轉換,是隱式轉換的一種,但如果我們光說到這兒就結束了,那scala也沒啥好學的了。
我們以上所說的自動轉換,都是帶有關係的,例如繼承的關係或者精度的關係。考慮一種情況,如果我們想要在兩個無關類型中進行轉換,那麼scala中的隱式轉換能實現嗎。
這就需要我們自定義轉換規則,讓其實現隱式轉換。
如何實現隱式轉換
我們自定義的隱式轉換,是需要函數實現的,即隱式轉換函數,隱式轉換函數是以implicit關鍵字聲明的帶有單個參數的函數,這種函數將會自動應用,將值從一種類型轉換到另一種類型。
快速入門例子
show me the code:
def main(args: Array[String]): Unit = {
// implicit 修飾的隱式函數
implicit def transform(d:Double):Int={
d.toInt
}
val i:Int = 5.0
println(i)
}
implicit關鍵字修飾的函數爲隱式函數,在這個例子中,先看i,類型定義爲Int類型,但是賦值5.0,這裏類型存在不同,編譯器在無法自動轉換該兩種類型的時候,查找自定義轉換規則,即查找implicit關鍵字,發現該處隱式轉換函數的類型正好符合,於是進行了隱式轉換。
如果我們把參數的類型或返回值類型進行改變,轉換是否會成功呢?
def main(args: Array[String]): Unit = {
//返回值 從Int改爲Float
implicit def transform(d:Double):Float={
d.toInt
}
val i:Int = 5.0
println(i)
}
運行我們會發現,我們無法實現轉換,編譯器會報錯。
type mismatch;
found : Double(5.0)
required: Int
val i:Int = 5.0
改變參數名和參數類型的例子可自己嘗試
從上面的例子可以看出,隱式轉換函數的函數名可以是任意的,隱式轉換隻與函數的參數類型和返回值類型有關,與函數名無關。
我們接着再來看,如果連續寫兩個隱式函數,參數和返回值類型相同,函數名不同,會如何呢?
def main(args: Array[String]): Unit = {
implicit def transform(d:Double):Int={
d.toInt
}
implicit def transform1(d:Double):Int={
d.toInt
}
val i:Int = 5.0
println(i)
}
執行,發現會出現如下錯誤
Note that implicit conversions are not applicable because they are ambiguous:
both method transform1 of type (d: Double)Int
and method transform of type (d: Double)Int
are possible conversion functions from Double(5.0) to Int
val i:Int = 5.0
表示,這裏有多個相同作用的隱私轉換函數,編譯器無法判斷用哪個合適。因此我們應當保證,隱式函數可以有多個,但同一作用域不能有多個相同類型的轉換規則。
小結:
- 隱式轉換函數是以implicit關鍵字聲明的帶有單個參數的函數
- 隱式轉換隻與函數的參數類型和返回值類型有關,與函數名無關
- 同一作用域不能有多個相同類型的轉換規則
強大的擴展功能
在當前的程序中,如果想要給一個已有的類增加新方法是非常簡單的,但是實際項目中,如果想要增加新方法,就需要修改源代碼,一旦修改源代碼,就可能違背了OCP開閉原則,這是難以接受的,因此在這種情況下,可以通過隱式轉換函數給類動態增加功能。
還是拿個栗子🌰來代替乾巴巴的解釋:
現在我們開發了一個Mysql的類,裏面只包含了select方法,這個已經提交到了代碼庫中,不管什麼原因吧,我們無法對Mysql類源代碼進行修改。
class Mysql {
def select(): Unit = {
//此處省去很多行代碼
}
}
def main(args: Array[String]): Unit = {
val mysql = new Mysql()
mysql.select()
}
在後來的需求迭代中,需要爲mysql增加一個delete方法,但是不能修改源代碼,我們此刻就可以通過隱式轉換爲其擴展功能
class Operater {
def delete(): Unit = {
//此處省去很多行代碼
}
}
def main(args: Array[String]): Unit = {
val mysql = new Mysql()
mysql.select()
implicit def transfrom(mysql: Mysql): Operater = {
new Operater()
}
mysql.delete()
}
有哪些隱式轉換
隱式方法
我們上文所用的入門案例,即是隱式方法,此處不再贅述。
隱式值
隱式值也叫隱式變量,將某個參數標記爲implicit,編譯器會在方法調用省略參數的情況下搜索作用域內的隱式值作爲默認參數。不得不吐槽,這句話亂七八糟的,但是沒辦法,scala這個語法就是這麼有點亂。
應用案例:
def main(args: Array[String]): Unit = {
implicit val nameTr:String = "關羽"
def function(implicit name:String):Unit={
println(name)
}
function("趙雲")
function
}
輸出:
趙雲
關羽
按照定義來解釋,當方法調用傳參的時候,scala使用傳遞的參數,當方法調用無參的時候,scala使用隱式轉換,將nameTr轉換到name。需要注意的是,nameTr和name的類型必須一致才能實現轉換
隱式類
在scala2.0之後提供了隱式類,可以使用implict聲明類,隱式類同樣可以擴展類的功能,比前面使用隱式方法轉換豐富類庫功能更加方便。
隱式類使用有如下幾個特點:
- 類的構造函數有且只能有一個,這個參數即是需要轉換的對象
- 隱式類只能定義在類、伴生對象或包對象內部,不能是頂級的
- 隱式類不能是case class
由於隱式類比隱式方法轉換豐富類庫功能更加方便,因此我們通過改造前面的Mysql的例子進行對比,我們可以通過只定義一個隱式類就可以實現功能拓展。
class Mysql {
def select(): Unit = {
}
}
def main(args: Array[String]): Unit = {
val mysql = new Mysql()
mysql.select()
//注意此處構造方法,傳入了Mysql的對象
implicit class Operater(mysql1:Mysql) {
def delete(): Unit = {
}
}
mysql.delete()
}
最後
學習scala不易,這篇文章揭開了隱式轉換神祕的面紗,從隱式轉換的介紹,到使用,到分類,儘量用通俗的語言和詳細的demo案例解釋清楚,希望能給不熟悉scala隱式轉換的小夥伴一點幫助。