關(guān)于 Scala 界定、隱式轉(zhuǎn)換的一些知識(六)——隱式轉(zhuǎn)換 和 隱式參數(shù)

1. 隱式轉(zhuǎn)換簡介

在scala語言當(dāng)中律歼,隱式轉(zhuǎn)換是一項(xiàng)強(qiáng)大的程序語言功能民镜,它不僅能夠簡化程序設(shè)計(jì),也能夠使程序具有很強(qiáng)的靈活性险毁。

在scala語言中制圈,隱式轉(zhuǎn)換是無處不在的,只不過scala語言為我們隱藏了相應(yīng)的細(xì)節(jié)畔况,例如scala中的類繼承層次結(jié)構(gòu)中:



它們存在固有的隱式轉(zhuǎn)換鲸鹦,不需要人工進(jìn)行干預(yù),例如Float在必要情況下自動(dòng)轉(zhuǎn)換為Double類型

在前面 關(guān)于 Scala 界定跷跪、隱式轉(zhuǎn)換的一些知識(三)——視圖界定 中我們也提到馋嗜,視圖界定可以跨越類層次結(jié)構(gòu)進(jìn)行,它背后的實(shí)現(xiàn)原理就是隱式轉(zhuǎn)換吵瞻,例如 Int 類型會視圖界定中會自動(dòng)轉(zhuǎn)換成 RichInt, 而 RichInt 實(shí)現(xiàn)了 Comparable 接口嵌戈,當(dāng)然這里面的隱式轉(zhuǎn)換也是 scala 語言為我們設(shè)計(jì)好的

本節(jié)將對隱式轉(zhuǎn)換中的隱式轉(zhuǎn)換函數(shù)覆积、隱式轉(zhuǎn)換規(guī)則、隱式參數(shù)進(jìn)行介紹熟呛,使大家明白如何自己實(shí)現(xiàn)隱式轉(zhuǎn)換操作宽档。

2. 隱式轉(zhuǎn)換函數(shù)

我們在介紹 視圖界定的時(shí)候,提到 視圖界定 的實(shí)現(xiàn) 用到了隱式轉(zhuǎn)換庵朝,并用 Int 隱式轉(zhuǎn)換成 RichInt 來舉例吗冤。而下面我們將再舉一個(gè)例子。

下列賦值如果沒有隱式轉(zhuǎn)換的話會報(bào)錯(cuò):
val x:Int = 3.5
添加隱式轉(zhuǎn)換函數(shù)后可以實(shí)現(xiàn) Double 類型到 Int 類型的賦值:

implicit def double2Int(x:Double) = x.toInt
val x:Int = 3.5

隱式轉(zhuǎn)換功能十分強(qiáng)大九府,可以快速地?cái)U(kuò)展現(xiàn)有類庫的功能贡歧,例如下面的代碼:

package cn.zyb

import java.io.File
import scala.io.Source

class RichFile(val file: File) {
    def read = Source.fromFile(file).getLines().mkString
}

object ImplicitFunction extends App {
    implicit def double2Int(x: Double) = x.toInt
    var x: Int = 3.5

    //隱式函數(shù)將java.io.File隱式轉(zhuǎn)換為 RichFile 類
    implicit def file2RichFile(file: File) = new RichFile(file)

    val f = new File("file.log").read
    println(f)
}

3. 隱式轉(zhuǎn)換規(guī)則

我們下面會介紹什么時(shí)候會發(fā)生隱式轉(zhuǎn)換浓冒,什么時(shí)候不會發(fā)生肠骆。

3.1 何時(shí)觸發(fā)隱式轉(zhuǎn)換

什么時(shí)候會發(fā)生隱式轉(zhuǎn)換呢加矛?主要有以下幾種情況:

3.1.1 當(dāng)方法中參數(shù)的類型與實(shí)際類型不一致時(shí)
def f(x: Int) = x + 1
//方法中輸入的參數(shù)類型與實(shí)際類型不一致,會自動(dòng)尋找 隱式轉(zhuǎn)換函數(shù) double2Int(x: Double)
//如果 double2Int(x: Double) 的參數(shù)儡羔,和 f(x: Int) 實(shí)際輸入?yún)?shù)類型一致
//且 double2Int 轉(zhuǎn)換后的返回類型 和 f(x: Int) 函數(shù)定義的參數(shù)類型一致宣羊,則滿足匹配
//double 類型會自動(dòng)調(diào)用 此隱式函數(shù),轉(zhuǎn)換為 Int 類型汰蜘,再進(jìn)行方法的執(zhí)行
f(3.14)
3.1.2 當(dāng)調(diào)用類中不存在的方法或成員時(shí)

例如上面 RichFile 的代碼:

//File類的對象并不存在 read 方法仇冯,此時(shí)便會發(fā)生隱式轉(zhuǎn)換
//將File類轉(zhuǎn)換成 RichFile
val f = new File("file.log").read

3.2 何時(shí)不會觸發(fā)隱式轉(zhuǎn)換

3.2.1 編譯器可以正常編譯通過時(shí)
//下面幾條語句,不需要自己定義隱式轉(zhuǎn)換編譯就可以通過
//因此它不會發(fā)生前面定義的隱式轉(zhuǎn)換
scala> 3.0*2
res0: Double = 6.0

scala> 2*3.0
res1: Double = 6.0

scala> 2*3.7
res2: Double = 7.4
3.2.2 轉(zhuǎn)換存在二義性
//這里定義了一個(gè)隱式轉(zhuǎn)換
implicit def file2RichFile(file:File)=new RichFile(file)
//這里又定義了一個(gè)隱式轉(zhuǎn)換族操,目的與前面那個(gè)相同
implicit def file2RichFile2(file:File)=new RichFile(file)

//下面這條語句在編譯時(shí)會出錯(cuò)苛坚,報(bào)錯(cuò)信息如下(去除了無用信息)
//both method file2RichFile and method file2RichFile2 
//are possible conversion functions from java.io.File to ?{def read: ?}
val f=new File("file.log").read
3.2.3 隱式轉(zhuǎn)換不會嵌套進(jìn)行

源類型到目標(biāo)類型的轉(zhuǎn)換只會進(jìn)行一次
下面看一下不完全代碼:

implicit def richFile2RichFileAnother(richFile: RichFile) = new RichFileAnother(richFile)

//RichFileAnother類,里面定義了read2方法
class RichFileAnother(val richFile: RichFile) {
    def read2 = file.read
}

//隱式轉(zhuǎn)換不會多次進(jìn)行色难,下面的語句會報(bào)錯(cuò)
//不能期望會發(fā)生 File 到 RichFile泼舱,然后 RifchFile 到 RichFileAnthoer 的轉(zhuǎn)換
val f = new File("file.log").read2

4. 隱式參數(shù)

我們在 關(guān)于 Scala 界定、隱式轉(zhuǎn)換的一些知識(五)——上下文界定 中枷莉,說到 上下文界定用到了 隱式參數(shù)柠掂。如果給函數(shù)定義隱式參數(shù)的話,則在使用時(shí)可以不帶參數(shù)

class Pair[T: Ordering](val first: T, val second: T) {
    //smaller 方法中有一個(gè)隱式參數(shù)依沮,該隱式參數(shù)類型為 Ordering[T]
    def smaller(implicit ord: Ordering[T]) = {
        if(ord.compare(first, second) > 0) first else second
    }
}

implicit val p1 = new PersonOrdering
//不給函數(shù)指定參數(shù),此時(shí)會查找一個(gè)隱式值枪狂,該隱式值類型為 Ordering[Person]
//根據(jù)上下文界定的要求危喉,p1 正好滿足要求
//因此它會作為 smaller 的隱式參數(shù)傳入,從而調(diào)用 ord.compare(first,second) 方法進(jìn)行比較
val p = new Pair(Person("123"), Person("456"))
//調(diào)用時(shí)并沒有傳入隱式參數(shù)
p.smaller

5. 隱式參數(shù)中的隱式轉(zhuǎn)換

前一講中州疾,我們提到函數(shù)中如果存在隱式參數(shù)辜限,在使用該函數(shù)時(shí) 編譯器會自動(dòng)幫我們搜索相應(yīng)的隱式值,并將該隱式值作為函數(shù)的參數(shù)
這里面其實(shí)沒有涉及到隱式轉(zhuǎn)換严蓖,本節(jié)將演示如何利用隱式參數(shù)進(jìn)行隱式轉(zhuǎn)換薄嫡,下面的代碼給定的是一個(gè)普通的比較函數(shù):

object ImplicitParameter extends App {
    //下面的代碼不能編譯通過氧急,這里面泛型T沒有具體指定
    //它不能直接使用 < 符號進(jìn)行比較
    def compare[T](first: T, second: T) = {
        if (first < second) first else second
    }
}

面的代碼要想使其編譯通過,可以為 T 指定其上界為 Ordered[T]

object ImplicitParameter extends App {
    def compare[T <: Ordered[T]](first: T, second: T) = {
        if (first < second) first else second
    }
}

這是一種解決方案毫深,我們還有一種解決方案就是通過隱式參數(shù)的隱式轉(zhuǎn)換來實(shí)現(xiàn)吩坝,代碼如下:

object ImplicitParameter extends App {
    def compare[T](first: T, second: T)(implicit order: T => Ordered[T]) = {
        if (first < second) first else second
    }
}

6. 隱式轉(zhuǎn)換問題梳理

6.1 多次隱式轉(zhuǎn)換問題

3.2.3 隱式轉(zhuǎn)換不會嵌套進(jìn)行 中我們提到,源類型到目標(biāo)類型的轉(zhuǎn)換只會進(jìn)行一次哑蔫,并不是說不存在多次隱式轉(zhuǎn)換钉寝,在一般的方法調(diào)用過程中可能會出現(xiàn)多次隱式轉(zhuǎn)換,例如:

class ClassA {
    override def toString() = "This is Class A"
}
class ClassB {
    override def toString() = "This is Class B"
}
class ClassC {
    override def toString() = "This is  ClassC"
    def printC(c: ClassC) = println(c)
}
class ClassD

object ImplicitWhole extends App {
    implicit def B2C(b: ClassB) = {
        println("B2C")
        new ClassC
    }
    implicit def D2C(d: ClassD) = {
        println("D2C")
        new ClassC
    }
    //下面的代碼會進(jìn)行兩次隱式轉(zhuǎn)換
    //因?yàn)镃lassD中并沒有printC方法
    //因?yàn)樗鼤[式轉(zhuǎn)換為ClassC(這是第一次,D2C)
    //然后調(diào)用printC方法
    //但是printC方法只接受ClassC類型的參數(shù)
    //然而傳入的參數(shù)類型是ClassB
    //類型不匹配闸迷,從而又發(fā)生了一次隱式轉(zhuǎn)地?fù)Q(這是第二次,B2C)
    //從而最終實(shí)現(xiàn)了方法的調(diào)用
    new ClassD().printC(new ClassB)
}

還有一種情況也會發(fā)生多次隱式轉(zhuǎn)換嵌纲,如果給函數(shù)定義了隱式參數(shù),在實(shí)際執(zhí)行過程中可能會發(fā)生多次隱式轉(zhuǎn)換腥沽,代碼如下:

object Main extends App {
    class PrintOps() {
        def print(implicit i: Int) = println(i);
    }

    implicit def str2PrintOps(s: String) = {
        println("str2PrintOps")
        new PrintOps
    }

    implicit def str2int(implicit s: String): Int = {
        println("str2int")
        Integer.parseInt(s)
    }

    implicit def getString = {
        println("getString")
        "123"
    }
    //下面的代碼會發(fā)生三次隱式轉(zhuǎn)換
    //首先編譯器發(fā)現(xiàn)String類型是沒有print方法的
    //嘗試隱式轉(zhuǎn)換逮走,利用str2PrintOps方法將String
    //轉(zhuǎn)換成PrintOps(第一次)
    //然后調(diào)用print方法,但print方法接受整型的隱式參數(shù)
    //此時(shí)編譯器會搜索隱式值今阳,但程序里面沒有給定师溅,此時(shí)
    //編譯器會嘗試調(diào)用 str2int方法進(jìn)行隱式轉(zhuǎn)換,但該方法
    //又接受一個(gè)implicit String類型參數(shù)酣栈,編譯器又會嘗試
    //查找一個(gè)對應(yīng)的隱式值险胰,此時(shí)又沒有,因此編譯器會嘗試調(diào)用
    //getString方法對應(yīng)的字符串(這是第二次隱式轉(zhuǎn)換矿筝,
    //獲取一個(gè)字符串起便,從無到有的過程)
    //得到該字符串后,再調(diào)用str2int方法將String類型字符串
    //轉(zhuǎn)換成Int類型(這是第三次隱式轉(zhuǎn)換)
    "a".print
}

6.2 要不要用隱式轉(zhuǎn)換的問題

從上述代碼中可以看到窖维,隱式轉(zhuǎn)換功能很強(qiáng)大榆综,但同時(shí)也帶來了程序復(fù)雜性性問題,在一個(gè)程序中如果大量運(yùn)用隱式轉(zhuǎn)換铸史,特別是涉及到多次隱式轉(zhuǎn)換時(shí)鼻疮,會使代碼理解起來變得比較困難,那到底要不要用隱式轉(zhuǎn)換呢琳轿?下面給出我自己開發(fā)實(shí)踐中的部分總結(jié)判沟,供大家參考:

  1. 即使你能輕松駕馭 scala 語言中的隱式轉(zhuǎn)換,能不用隱式轉(zhuǎn)換就盡量不用
  2. 如果一定要用崭篡,在涉及多次隱式轉(zhuǎn)換時(shí)挪哄,必須要說服自己這樣做的合理性
  3. 如果只是炫耀自己的scala語言能力,請大膽使用

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末琉闪,一起剝皮案震驚了整個(gè)濱河市迹炼,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖斯入,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件砂碉,死亡現(xiàn)場離奇詭異,居然都是意外死亡刻两,警方通過查閱死者的電腦和手機(jī)增蹭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來闹伪,“玉大人沪铭,你說我怎么就攤上這事∑浚” “怎么了杀怠?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長厅克。 經(jīng)常有香客問我赔退,道長,這世上最難降的妖魔是什么证舟? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任硕旗,我火速辦了婚禮,結(jié)果婚禮上女责,老公的妹妹穿的比我還像新娘漆枚。我一直安慰自己,他們只是感情好抵知,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布墙基。 她就那樣靜靜地躺著,像睡著了一般刷喜。 火紅的嫁衣襯著肌膚如雪残制。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天掖疮,我揣著相機(jī)與錄音初茶,去河邊找鬼。 笑死浊闪,一個(gè)胖子當(dāng)著我的面吹牛恼布,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播搁宾,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼折汞,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了猛铅?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤凤藏,失蹤者是張志新(化名)和其女友劉穎奸忽,沒想到半個(gè)月后堕伪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡栗菜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年欠雌,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片疙筹。...
    茶點(diǎn)故事閱讀 40,133評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡富俄,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出而咆,到底是詐尸還是另有隱情霍比,我是刑警寧澤,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布暴备,位于F島的核電站悠瞬,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏涯捻。R本人自食惡果不足惜浅妆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望障癌。 院中可真熱鬧凌外,春花似錦、人聲如沸涛浙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蝗拿。三九已至晾捏,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間哀托,已是汗流浹背惦辛。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留仓手,地道東北人胖齐。 一個(gè)月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像嗽冒,于是被迫代替她去往敵國和親呀伙。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評論 2 355

推薦閱讀更多精彩內(nèi)容