implicit 是scala一個關鍵字,使scala更靈活和容易擴展浴栽。
1.隱式轉換函數(shù)
implicit def int2str(x:Int):String = x.toString
2.隱式類
implicit class Box(x: Int) {? ?
}
3.隱式參數(shù)
def compare[T](x:T,y:T)(implicit ordered: Ordering[T]):Int = {
? ordered.compare(x,y)
}
4.隱式值
implicit val x: Int = 0
5.隱式對象
implicit object obj {
}
6.context bound
def compare2[T: Ordering](x: T, y: T) = {
? val ord = implicitly[Ordering[T]]
? ord.compare(x, y)
}
一荒叼、隱式轉換函數(shù)、Overview
首先來看隱式轉換函數(shù)典鸡。
implicit def int2str(x:Int):String = x.toString1
聲明了一個隱式函數(shù)int2str被廓。它告訴編譯器,這個函數(shù)是一個隱式轉換函數(shù)萝玷,把Int類型的值轉換String類型的值嫁乘。
如果在進行一個對Int類型的操作時不合法,編譯器會在當前作用域尋找合適的隱式轉換球碉,來嘗試使這種操作合法亦渗。隱式轉換發(fā)生在這兩種情景:
e是一個S類型的表達式,而需要的卻是T類型汁尺,編譯器會尋找S=>T的隱式轉換
e是一個S類型的表達式法精,使用點號訪問e.m時,m不是類型S的成員痴突,編譯器會尋找合適的隱式轉換使e.m合法
隱式轉換的用途就是擴展已有的類搂蜓,在不修改原有類的基礎上為其添加新的方法、成員辽装。
10.concat("hello")10.length
接受String類型的函數(shù)也可以接受Int類型:
def hi(x:String) = println("hi"+x)
hi(123)
需要注意:?
1.對于隱式轉換函數(shù)帮碰,編譯器最關心的是它的類型簽名,即它將哪一種類型轉換到另一種類型拾积,也就是說它應該接受只一個參數(shù)殉挽,對于接受多參數(shù)的隱式函數(shù)來說就沒有隱式轉換的功能了丰涉。
implicit def int2str(x:Int):String = x.toString // 正確implicit def int2str(x:Int,y:Int):String = x.toString // 錯誤
2.不支持嵌套的隱式轉換
class A{
def hi = println("hi")
}
implicit def int2str(x:Int):String = x.toString
implicit def str2A(x:Int,y:Int):A = new A"str".hi? // 正確1.hi? ? ? // 錯誤
3.不能存在二義性,即同一個作用域不能定義兩個相同類型的隱式轉換函數(shù)斯碌,這樣編譯器將無法決定使用哪個轉換
/* 錯誤-- */implicit def int2str(x:Int):String = x.toString
implicit def anotherInt2str(x:Int):A = x.toString/* --錯誤 */
4.代碼能夠在不使用隱式轉換的前提下能編譯通過一死,就不會進行隱式轉換
二、隱式類
前面提到傻唾,隱式轉換最重要的應用是擴展已存在的類投慈,它的功能和c#中的擴展方法很類似。比如我們想對已有的Int類型添加一個sayhi的方法冠骄,可以這樣做:
class SayhiImpl(ivalue:Int) {
? val value:Int = ivalue
? def sayhi = println(s"Hi $value!")
}
implicit def int2Sayhi(x:Int) = new SayhiImpl(x)
那么調用123.sayhi伪煤,將會輸出:Hi 123!。
即我們先實現(xiàn)一個支持sayhi方法的類凛辣,再寫一個隱式轉換函數(shù)抱既,使得Int類也支持sayhi。但是這種寫法過于啰嗦了扁誓,可以使用隱式類實現(xiàn)等價的功能:
implicit class SayhiImpl(ivalue:Int) {
? val value:Int = ivalue
? def sayhi = println(s"Hi $value!")
}123.sayhi? //合法
隱式類就是在類定義前加一個implicit關鍵字防泵,這表示它的構造函數(shù)是一個隱式轉換函數(shù),能夠將參數(shù)的類型轉換成自己的類型跋理,在這里就是構造函數(shù)SayhiImpl(ivalue:Int)定義了Int到SayhiImpl的隱式轉換。
在使用隱式類時需要注意以下限制條件
1.只能在別的trait/類/對象內部定義恬总。
? ? object Helpers {
? ? ? implicit class RichInt(x: Int) // 正確前普!
? ? }
? ? implicit class RichDouble(x: Double) // 錯誤!
2.構造函數(shù)只能攜帶一個非隱式參數(shù)壹堰。
implicit class RichDate(date: java.util.Date) // 正確拭卿!
implicit class Indexer[T](collecton: Seq[T], index: Int) // 錯誤!
implicit class Indexer[T](collecton: Seq[T])(implicit index: Index) // 正確
雖然我們可以創(chuàng)建帶有多個非隱式參數(shù)的隱式類贱纠,但這些類無法用于隱式轉換峻厚。
3.implict關鍵字不能用于case類
implicit case class Baz(x: Int) // 錯誤!
三谆焊、隱式參數(shù)
看最開始的例子:
def compare[T](x:T,y:T)(implicit ordered: Ordering[T]):Int = {
? ordered.compare(x,y)
}
在函數(shù)定義的時候惠桃,支持在最后一組參數(shù)使用?implicit,表明這是一組隱式參數(shù)辖试。在調用該函數(shù)的時候辜王,可以不用傳遞隱式參數(shù),而編譯器會自動尋找一個implict標記過的合適的值作為該參數(shù)罐孝。
例如上面的函數(shù)呐馆,調用compare時不需要顯式提供ordered,而只需要直接compare(1,2)這樣使用即可莲兢。
再舉一個例子:
object Test{
? ? trait Adder[T] {
? ? ? def add(x:T,y:T):T
? ? }
? ? implicit val a = new Adder[Int] {
? ? ? override def add(x: Int, y: Int): Int = x+y
? ? }
? ? def addTest(x:Int,y:Int)(implicit adder: Adder[Int]) = {
? ? ? adder.add(x,y)
? ? }
? addTest(1,2)? ? ? // 正確, = 3? addTest(1,2)(a)? // 正確, = 3? addTest(1,2)(new Adder[Int] {
? ? ? override def add(x: Int, y: Int): Int = x-y
? ? })? // 同樣正確, = -1}
Adder是一個trait汹来,它定義了add抽象方法要求子類必須實現(xiàn)续膳。
addTest函數(shù)擁有一個Adder[Int]類型的隱式參數(shù)。
在當前作用域里存在一個Adder[Int]類型的隱式值implicit val a收班。
在調用addTest時坟岔,編譯器可以找到implicit標記過的a,所以我們不必傳遞隱式參數(shù)而是直接調用addTest(1,2)闺阱。而如果你想要傳遞隱式參數(shù)的話炮车,你也可以自定義一個傳給它,像后兩個調用所做的一樣酣溃。
四瘦穆、隱式值和隱式對象
最開始的示例代碼有,
隱式值:
implicit val x: Int = 0
隱式對象:
implicit object obj {
}
上面提到過赊豌,在調用含有隱式參數(shù)的函數(shù)時扛或,編譯器會自動尋找合適的隱式值當做隱式參數(shù),而只有用implict標記過的值碘饼、對象熙兔、函數(shù)才能被找到。
例如自動尋找隱式對象:
? ? implicit object Obj {
? ? ? def hello(s:String) = println(s"Hello $s!")
? ? }
? ? def test2(s:String)(implicit o: Obj.type ) = {
? ? ? o.hello(s)
? ? }
? test2("world")? // 輸出Hello world!
自動尋找隱式函數(shù):
? ? implicit def int2str(x: Int): String = x.toString
? ? def test1(x: Int, func: String => Unit)
? ? ? ? ? ? (implicit helper: Int => String) = {
? ? ? func("\"" + helper(x) + "\"")
? ? }
? ? test1(12, println) // 打印出"12"
五艾恼、context bound
最后來看隱式作為泛型類型的限制:
def compare2[T: Ordering](x: T, y: T) = {
? val ord = implicitly[Ordering[T]]
? ord.compare(x, y)
}
上面compare2是一個泛型函數(shù)住涉,其有一個類型參數(shù)T,在這里T:Ordering對T類型做出了限制钠绍,要求必須存在一個Ordering[T]類型的隱式值舆声,這種限制就叫做context bound。
這其實是隱式參數(shù)的語法糖柳爽,它等價于:
def compare2[T](x: T, y: T)(implicit ord:Ordering[T]) = {
? ord.compare(x, y)
}
注意到前面的函數(shù)體里用到了implicitly函數(shù)媳握。這是因為在使用[T: Ordering]這樣的類型限制時,我們沒有能接觸到具體的Ordering[T]類型的隱式值ord磷脯,這時候調用implicitly函數(shù)蛾找,就可以拿到這個隱式值,進而進行下一步操作了赵誓。沒有這個函數(shù)的話打毛,你需要這樣寫:
def compare2[T: Ordering](x: T, y: T) = {
? def helper(implicit ord:Ordering[T]) == ord.compare(x, y)
? helper
}
隱式解析機制
最后加一點比較深入一點的內容,看一下隱式值的尋找機制
即編譯器是如何查找到缺失信息的俩功,解析具有以下兩種規(guī)則:
1 .首先會在當前代碼作用域下查找隱式實體(隱式方法 隱式類 隱式對象)?
2.如果第一條規(guī)則查找隱式實體失敗隘冲,會繼續(xù)在隱式參數(shù)的類型的作用域里查找
類型的作用域是指與該類型相關聯(lián)的全部伴生模塊,一個隱式實體的類型T它的查找范圍如下:
(1)如果T被定義為T with A with B with C,那么A,B,C都是T的部分绑雄,在T的隱式解析過程中展辞,它們的伴生對象都會被搜索
(2)如果T是參數(shù)化類型,那么類型參數(shù)和與類型參數(shù)相關聯(lián)的部分都算作T的部分万牺,比如List[String]的隱式搜索會搜索List的?
伴生對象和String的伴生對象
(3) 如果T是一個單例類型p.T罗珍,即T是屬于某個p對象內洽腺,那么這個p對象也會被搜索
(4) 如果T是個類型注入S#T,那么S和T都會被搜索