Chapter 21《Implicit Conversions and Parameters》

隱式轉(zhuǎn)換和隱式參數(shù)

  • 如果使用別人的代碼庫(kù)华匾,無(wú)法進(jìn)行修改,Scala進(jìn)行擴(kuò)展的方法是隱式轉(zhuǎn)換和隱式參數(shù)。允許省略掉冗余且明顯的細(xì)節(jié)。

隱式轉(zhuǎn)換
  • 隱式轉(zhuǎn)換通常在兩個(gè)開(kāi)發(fā)完全不知道對(duì)方存在的軟件或類庫(kù)時(shí)非常有用肉盹。如果雙方都描述了同一樣概念,隱式轉(zhuǎn)換可以減少?gòu)囊粋€(gè)類型顯式轉(zhuǎn)換成另一個(gè)類型的需要疹尾。

隱式規(guī)則

    1. 隱式定義指的是那些我們?cè)试S編譯器插入程序以解決類型錯(cuò)誤的定義上忍。如果x + y不能通過(guò)編譯,編譯器可能會(huì)改為convert(x) + y纳本,其中convert是某種可用的隱式轉(zhuǎn)換窍蓝,如果convert(x)的對(duì)象支持+動(dòng)作,那么這個(gè)改動(dòng)就可能修復(fù)程序繁成,讓它通過(guò)類型檢查并正確運(yùn)行吓笙。如果convert真的是某種簡(jiǎn)單的轉(zhuǎn)換函數(shù),可以不顯式地寫出這個(gè)方法巾腕。
    1. 隱式轉(zhuǎn)換受如下規(guī)則的約束:
      1. 標(biāo)記規(guī)則:只有標(biāo)記為implicit的定義才可用面睛。implicit用來(lái)標(biāo)記哪些聲明可被編譯器用作隱式定義。編譯器只會(huì)從那些顯式標(biāo)記為implicit的定義中選擇祠墅。
      1. 作用域規(guī)則:被插入的隱式轉(zhuǎn)換必須是當(dāng)前作用域的單個(gè)標(biāo)識(shí)符侮穿,而且跟隱式轉(zhuǎn)換的源類型或者目標(biāo)類型有關(guān)聯(lián)歌径。必須將隱式轉(zhuǎn)換引入到當(dāng)前作用域才能使得它們可用毁嗦,例如,編譯器不會(huì)引入varaiable.convert這種語(yǔ)法回铛,必須要引入它狗准,使其成為單個(gè)標(biāo)識(shí)符克锣。
      • 2.1. 編譯器會(huì)在隱式轉(zhuǎn)換的源類型或目標(biāo)類型的伴生對(duì)象中查找隱式定義。例如嘗試將Dollar對(duì)象傳遞給一個(gè)接收Euro的函數(shù)腔长,編譯器會(huì)在DollarEuro的伴生對(duì)象中尋找隱式轉(zhuǎn)換袭祟。在伴生對(duì)象中定義的隱式轉(zhuǎn)換可以不引入直接使用。就是隱式定義是在當(dāng)前作用域有效的捞附,而不是全部有效巾乳。
      1. 每次一個(gè)規(guī)則:每次只能有一個(gè)隱式定義被插入。
      1. 顯式優(yōu)先原則:只要代碼按照編寫的樣子能夠通過(guò)類型檢查鸟召,就不會(huì)啟動(dòng)隱式轉(zhuǎn)換让禀。
    1. 命名一個(gè)隱式轉(zhuǎn)換:隱式轉(zhuǎn)換可以使用任何名稱拂铡,名稱并不重要,只有在顯式引入該轉(zhuǎn)換的時(shí)候使用以及決定在程序的某個(gè)位置都有哪些隱式轉(zhuǎn)換可用時(shí)用到。例如弊添,在一個(gè)對(duì)象中有兩個(gè)隱式轉(zhuǎn)換,intToString裙顽,StringToStringWrapper蛙婴,如果只想將String轉(zhuǎn)換為StringWrapper,而不想將Int轉(zhuǎn)換為String舔糖,可以只引入第二個(gè)隱式轉(zhuǎn)換娱两。
  • 4.嘗試隱式轉(zhuǎn)換的地方:1.轉(zhuǎn)換到一個(gè)預(yù)期的類型;2.對(duì)某個(gè)選擇接收端的轉(zhuǎn)換金吗;3.隱式參數(shù)
A. 隱式轉(zhuǎn)換到一個(gè)預(yù)期的類型
  • 當(dāng)編譯器發(fā)現(xiàn)X而需要Y的時(shí)候谷婆,查找能夠?qū)?code>X轉(zhuǎn)換為Y的隱式轉(zhuǎn)換。注意隱式轉(zhuǎn)換的引入需要在使用之前辽聊,不然編譯器不會(huì)發(fā)現(xiàn)這個(gè)隱式轉(zhuǎn)換纪挎。
    scala> val i: Int = 3.5
    <console>:7: error: type mismatch;
    scala> implicit def doubleToInt(x: Double) = x.toInt
    doubleToInt: (x: Double)Int //這個(gè)隱式轉(zhuǎn)換需要放在定義語(yǔ)句之前。
    
    doubleToInt是單個(gè)標(biāo)識(shí)符跟匆,如果不是定義在當(dāng)前作用域中异袄,可以使用import或者extend或者with特質(zhì)來(lái)導(dǎo)入。類似Double轉(zhuǎn)向Int這種通用類型轉(zhuǎn)向受限類型的轉(zhuǎn)換會(huì)丟失精度玛臂,但是反方向的轉(zhuǎn)換是定義得通的烤蜕,需要自己定義。在Predef中有從IntDouble的轉(zhuǎn)換迹冤。
B. 轉(zhuǎn)換接收端
  • 隱式轉(zhuǎn)換還能應(yīng)用關(guān)于方法調(diào)用的接收端讽营,也就是方法被調(diào)用的那個(gè)對(duì)象。

  • 這個(gè)隱式轉(zhuǎn)換主要有兩種用途泡徙,1.接收端轉(zhuǎn)換允許我們更平滑地將新類繼承到已有的類繼承關(guān)系圖譜中橱鹏,2.支持在語(yǔ)言中編寫領(lǐng)域特定語(yǔ)言(DSL)。假如寫下obj.doIt,但是obj中并不存在名為doIt的成員莉兰,編譯器會(huì)在放棄之前嘗試插入轉(zhuǎn)換挑围。在本例中,這個(gè)轉(zhuǎn)換需要應(yīng)用于接收端糖荒,也就是obj杉辙,編譯器會(huì)尋找obj到預(yù)期類型的轉(zhuǎn)換,這個(gè)預(yù)期類型中擁有名為doIt的成員捶朵。

    1. 與新類型互操作
    • 接收端轉(zhuǎn)換的一個(gè)主要用途是讓新類型和已有類型的集成更順滑蜘矢。可以讓客戶端的代碼不改變综看,就像是在使用新類型一樣硼端。1 + new Rational(3,4) 可插入一個(gè)隱式轉(zhuǎn)換,最后變成intToRational(1) + new Rational(3,4)寓搬,完美解決Int類型中沒(méi)有+(rational: Rational)這個(gè)方法珍昨。
    2. 模擬新的語(yǔ)法
    • 隱式轉(zhuǎn)換的另一個(gè)用途是模擬添加新的語(yǔ)法,例如Map中的語(yǔ)法:Map(1 -> "1")句喷,整體的操作過(guò)程是這樣的镣典。編譯器插入any2ArrowAssoc的轉(zhuǎn)換,將1轉(zhuǎn)換為帶有->方法的ArrowAssoc的對(duì)象唾琼,從而可以調(diào)用->兄春,這看起來(lái)就像是一個(gè)新的語(yǔ)法。如果某個(gè)對(duì)象調(diào)用了不屬于自己的方法锡溯,那么很有可能是使用了隱式轉(zhuǎn)換赶舆。可以使用這些富包裝類模式做出以類庫(kù)形式定義的內(nèi)部DSL祭饭。
    3. 隱式類
    • Scala 2.10引入了隱式類來(lái)簡(jiǎn)化富包裝類的編寫芜茵。隱式類是以implicit打頭的類,對(duì)于這樣的類倡蝙,編譯器會(huì)生成一個(gè)從類的構(gòu)造方法參數(shù)到類本身的隱式轉(zhuǎn)換九串。例如,
    case class Rectangle(width: Int, height: Int)
    

    如果經(jīng)常使用這個(gè)類寺鸥,可以使用富包裝類來(lái)簡(jiǎn)化構(gòu)造工作猪钮,定義:

    implicit class RectangleMaker(width: Int) {
    def x(height: Int) = Rectangle(width, height)
    }
    // Automatically generated
    implicit def RectangleMaker(width: Int) =
    new RectangleMaker(width)
    

    對(duì)于以上的隱式類,會(huì)自動(dòng)生成類構(gòu)造參數(shù)到該類對(duì)象的一個(gè)轉(zhuǎn)換胆建】镜停可以直接使用3 x 4這樣的形式。

    scala> val myRectangle = 3 x 4
    myRectangle: Rectangle = Rectangle(3,4)  
    

    并不是任何類定義前面度可以放implicit笆载,隱式類不能是樣例類扑馁,并且其構(gòu)造方法必須有且僅有一個(gè)參數(shù)涯呻。隱式類必須存在于一個(gè)對(duì)象、類或者特質(zhì)里面檐蚜。

C. 隱式參數(shù)
  • 編譯器會(huì)插入隱式定義的最后一個(gè)地方是參數(shù)列表魄懂,編譯器有時(shí)候會(huì)將someCall(a)替換為someCall(a)(b)沿侈,通過(guò)追加一個(gè)參數(shù)列表來(lái)完成某個(gè)函數(shù)的調(diào)用闯第。隱式參數(shù)提供的是整個(gè)最后一組currying的參數(shù)列表,而不僅僅是最后一個(gè)參數(shù)缀拭。例如咳短,someCall(a)可能根據(jù)實(shí)際情況被替換為someCall(a)(b, c, d)。如果要讓編譯器隱式地填充隱式參數(shù)蛛淋,首先需要定義這樣一個(gè)符合預(yù)期類型的變量咙好。填充的變量也必須聲明為implicit的,如果不是這樣褐荷,編譯器不會(huì)使用它來(lái)填充缺失的列表勾效。如果變量不是當(dāng)前作用域內(nèi)的單個(gè)標(biāo)識(shí)符,也不會(huì)被采納叛甫。implicit關(guān)鍵字是應(yīng)用到整個(gè)參數(shù)列表而不是單個(gè)參數(shù)的层宫。
    def greet(name: String)(implicit prompt: PreferredPrompt, drink: PreferredDrink) = {} 
    
  • 由于編譯器是在作用域內(nèi)通過(guò)類型匹配來(lái)填充隱式參數(shù),所以一般希望類型能夠特殊從而避免誤匹配的出現(xiàn)其监。隱式參數(shù)最常使用的場(chǎng)景是提供關(guān)于在更靠前的參數(shù)列表中已經(jīng)顯式提到的類的信息萌腿。
    def maxListOrdering[T](elements: List[T])(implicit ordering: Ordering[T]): T 
    
    隱式參數(shù)ordering說(shuō)明了已知類型T更多的信息。有了T抖苦,ordering的類型就已知毁菱,可以使用隱式參數(shù)隱式插入。
  • 隱式參數(shù)的代碼風(fēng)格锌历,最好是對(duì)隱式參數(shù)使用定制名稱的類型贮庞,比如PreferredPromptPreferredDrink,而不是StringString究西,而且這個(gè)類型命名時(shí)贸伐,至少使用一個(gè)能明確其智能的名字,比如Ordering怔揩。如果定義成為implicit (T,T) => T捉邢,該參數(shù)并沒(méi)有透露出任何關(guān)于該烈性用途的信息,范圍太廣商膊,很容易誤使用伏伐。

上下文界定
  • 當(dāng)一個(gè)參數(shù)是隱式定義的時(shí)候,在函數(shù)體內(nèi)又使用了這個(gè)隱式參數(shù)作為參數(shù)傳遞晕拆,這時(shí)可以將不用寫這個(gè)參數(shù)藐翎。
    def maxListOrdering[T](elements: List[T])(ordering: Ordering[T]): T = {
        val maxRest = maxListOrdering(rest)(ordering)  //這個(gè)ordering可以省略
    }
    def maxListOrdering[T](elements: List[T])(ordering: Ordering[T]): T = {
        if (ordering.gt(x, maxRest)) x else maxRest //為了避免使用ordering材蹬,
    //可以使用庫(kù)函數(shù)  > implicit[Ordering[T]],該函數(shù)返回的是Ordering[T]的隱式對(duì)象吝镣。
    //這樣ordering就可以隨意命名堤器。
    }
    
    由于上文中提到的模式很常用,Scala允許聲調(diào)這個(gè)參數(shù)的名稱并使用上下文界定來(lái)縮短方法簽名末贾。[T: Ordering]是一個(gè)上下文界定闸溃,context bound,做了兩件事情拱撵。1. 引入類型參數(shù)T辉川,2.添加一個(gè)Ordering[T]的隱式參數(shù),并不需要知道這個(gè)參數(shù)的名字拴测。最后的代碼如下:
    def maxList[T : Ordering](elements: List[T]): T =
    elements match {
    case List() =>
    throw new IllegalArgumentException("empty list!")
    case List(x) => x
    case x :: rest =>
    val maxRest = maxList(rest)
    if (implicitly[Ordering[T]].gt(x, maxRest)) x
    else maxRest
    }
    

當(dāng)有多個(gè)轉(zhuǎn)換可選時(shí)

  • 如果有多個(gè)轉(zhuǎn)換可選乓旗,Scala會(huì)拒絕插入,隱式轉(zhuǎn)換在這個(gè)轉(zhuǎn)換是顯而易見(jiàn)的并且純粹是樣板代碼的時(shí)候最好用集索。Scala目前采取的措施是使用可用轉(zhuǎn)換中更具體的轉(zhuǎn)換屿愚,具體體現(xiàn)在,該轉(zhuǎn)換的入?yún)㈩愋褪莿e的轉(zhuǎn)換的子類型务荆;兩者都是方法妆距,具體轉(zhuǎn)換所在的類擴(kuò)展自通用轉(zhuǎn)換所在的類。

調(diào)試

  • 調(diào)試的時(shí)候可以顯示地寫出來(lái)轉(zhuǎn)換用以明確編譯器插入的轉(zhuǎn)換到底是哪一個(gè)蛹含。也可以對(duì)編譯器設(shè)置選項(xiàng)-Xprint:typer毅厚,使用這個(gè)選項(xiàng)來(lái)運(yùn)行scalac,可以看到添加了隱式轉(zhuǎn)換之后的代碼浦箱。使用的方法:scalac -Xprint:typer test.scala吸耿。隱式轉(zhuǎn)換還是不能濫用的。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末酷窥,一起剝皮案震驚了整個(gè)濱河市咽安,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蓬推,老刑警劉巖妆棒,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異沸伏,居然都是意外死亡糕珊,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門毅糟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)红选,“玉大人,你說(shuō)我怎么就攤上這事姆另±撸” “怎么了坟乾?”我有些...
    開(kāi)封第一講書人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)蝶防。 經(jīng)常有香客問(wèn)我甚侣,道長(zhǎng),這世上最難降的妖魔是什么间学? 我笑而不...
    開(kāi)封第一講書人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任殷费,我火速辦了婚禮,結(jié)果婚禮上菱鸥,老公的妹妹穿的比我還像新娘宗兼。我一直安慰自己躏鱼,他們只是感情好氮采,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著染苛,像睡著了一般鹊漠。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上茶行,一...
    開(kāi)封第一講書人閱讀 51,365評(píng)論 1 302
  • 那天躯概,我揣著相機(jī)與錄音,去河邊找鬼畔师。 笑死娶靡,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的看锉。 我是一名探鬼主播姿锭,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼伯铣!你這毒婦竟也來(lái)了呻此?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤腔寡,失蹤者是張志新(化名)和其女友劉穎焚鲜,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體放前,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡忿磅,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了凭语。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片葱她。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖叽粹,靈堂內(nèi)的尸體忽然破棺而出览效,到底是詐尸還是另有隱情却舀,我是刑警寧澤,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布锤灿,位于F島的核電站挽拔,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏但校。R本人自食惡果不足惜螃诅,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望状囱。 院中可真熱鬧术裸,春花似錦、人聲如沸亭枷。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)叨粘。三九已至猾编,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間升敲,已是汗流浹背答倡。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留驴党,地道東北人瘪撇。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像港庄,于是被迫代替她去往敵國(guó)和親倔既。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354

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