隱式轉換指的是以implicit
關鍵字聲明帶有單個參數的轉換函數,它將值從一種類型轉換爲另外一種類型,以便使用以前類型所沒有的功能。示例以下:java
// 普通人 class Person(val name: String) // 雷神 class Thor(val name: String) { // 正常狀況下只有雷神才能舉起雷神之錘 def hammer(): Unit = { println(name + "舉起雷神之錘") } } object Thor extends App { // 定義隱式轉換方法 將普通人轉換爲雷神 一般建議方法名使用source2Target,即:被轉換對象To轉換對象 implicit def person2Thor(p: Person): Thor = new Thor(p.name) // 這樣普通人也能舉起雷神之錘 new Person("普通人").hammer() } 輸出: 普通人舉起雷神之錘
並非你使用implicit
轉換後,隱式轉換就必定會發生,好比上面若是不調用hammer()
方法的時候,普通人就仍是普通人。一般程序會在如下狀況下嘗試執行隱式轉換:git
而在如下三種狀況下編譯器不會嘗試執行隱式轉換:程序員
convert1(convert2(a))*b
;這裏首先解釋一下二義性,上面的代碼進行以下修改,因爲兩個隱式轉換都是生效的,因此就存在了二義性:github
//兩個隱式轉換都是有效的 implicit def person2Thor(p: Person): Thor = new Thor(p.name) implicit def person2Thor2(p: Person): Thor = new Thor(p.name) // 此時下面這段語句沒法經過編譯 new Person("普通人").hammer()
其次再解釋一下多個轉換的問題:編程
class ClassA { override def toString = "This is Class A" } class ClassB { override def toString = "This is Class B" def printB(b: ClassB): Unit = println(b) } class ClassC class ClassD object ImplicitTest extends App { implicit def A2B(a: ClassA): ClassB = { println("A2B") new ClassB } implicit def C2B(c: ClassC): ClassB = { println("C2B") new ClassB } implicit def D2C(d: ClassD): ClassC = { println("D2C") new ClassC } // 這行代碼沒法經過編譯,由於要調用到printB方法,須要執行兩次轉換C2B(D2C(ClassD)) new ClassD().printB(new ClassA) /* * 下面的這一行代碼雖然也進行了兩次隱式轉換,可是兩次的轉換對象並非一個對象,因此它是生效的: * 轉換流程以下: * 1. ClassC中並無printB方法,所以隱式轉換爲ClassB,而後調用printB方法; * 2. 可是printB參數類型爲ClassB,然而傳入的參數類型是ClassA,因此須要將參數ClassA轉換爲ClassB,這是第二次; * 即: C2B(ClassC) -> ClassB.printB(ClassA) -> ClassB.printB(A2B(ClassA)) -> ClassB.printB(ClassB) * 轉換過程1的對象是ClassC,而轉換過程2的轉換對象是ClassA,因此雖然是一行代碼兩次轉換,可是仍然是有效轉換 */ new ClassC().printB(new ClassA) } // 輸出: C2B A2B This is Class B
隱式轉換的能夠定義在如下三個地方:app
上面咱們使用的方法至關於直接定義在執行代碼的做用域中,下面分別給出其餘兩種定義的代碼示例:ide
定義在原類型的伴生對象中:函數
class Person(val name: String) // 在伴生對象中定義隱式轉換函數 object Person{ implicit def person2Thor(p: Person): Thor = new Thor(p.name) }
class Thor(val name: String) { def hammer(): Unit = { println(name + "舉起雷神之錘") } }
// 使用示例 object ScalaApp extends App { new Person("普通人").hammer() }
定義在一個公共的對象中:大數據
object Convert { implicit def person2Thor(p: Person): Thor = new Thor(p.name) }
// 導入Convert下全部的隱式轉換函數 import com.heibaiying.Convert._ object ScalaApp extends App { new Person("普通人").hammer() }
注:Scala自身的隱式轉換函數大部分定義在
Predef.scala
中,你能夠打開源文件查看,也能夠在Scala交互式命令行中採用:implicit -v
查看所有隱式轉換函數。this
在定義函數或方法時可使用標記爲implicit
的參數,這種狀況下,編譯器將會查找默認值,提供給函數調用。
// 定義分隔符類 class Delimiters(val left: String, val right: String) object ScalaApp extends App { // 進行格式化輸出 def formatted(context: String)(implicit deli: Delimiters): Unit = { println(deli.left + context + deli.right) } // 定義一個隱式默認值 使用左右中括號做爲分隔符 implicit val bracket = new Delimiters("(", ")") formatted("this is context") // 輸出: (this is context) }
關於隱式參數,有兩點須要注意:
1.咱們上面定義formatted
函數的時候使用了柯里化,若是你不使用柯里化表達式,按照一般習慣只有下面兩種寫法:
// 這種寫法沒有語法錯誤,可是沒法經過編譯 def formatted(implicit context: String, deli: Delimiters): Unit = { println(deli.left + context + deli.right) } // 不存在這種寫法,IDEA直接會直接提示語法錯誤 def formatted( context: String, implicit deli: Delimiters): Unit = { println(deli.left + context + deli.right) }
上面第一種寫法編譯的時候會出現下面所示error
信息,從中也能夠看出implicit
是做用於參數列表中每一個參數的,這顯然不是咱們想要到達的效果,因此上面的寫法採用了柯里化。
not enough arguments for method formatted: (implicit context: String, implicit deli: com.heibaiying.Delimiters)
2.第二個問題和隱式函數同樣,隱式默認值不能存在二義性,不然沒法經過編譯,示例以下:
implicit val bracket = new Delimiters("(", ")") implicit val brace = new Delimiters("{", "}") formatted("this is context")
上面代碼沒法經過編譯,出現錯誤提示ambiguous implicit values
,即隱式值存在衝突。
引入隱式參數和引入隱式轉換函數方法是同樣的,有如下三種方式:
咱們上面示例程序至關於直接定義執行代碼的上下文做用域中,下面給出其餘兩種方式的示例:
定義在隱式參數對應類的伴生對象中;
class Delimiters(val left: String, val right: String) object Delimiters { implicit val bracket = new Delimiters("(", ")") }
// 此時執行代碼的上下文中不用定義 object ScalaApp extends App { def formatted(context: String)(implicit deli: Delimiters): Unit = { println(deli.left + context + deli.right) } formatted("this is context") }
統必定義在一個文件中,在使用時候導入:
object Convert { implicit val bracket = new Delimiters("(", ")") }
// 在使用的時候導入 import com.heibaiying.Convert.bracket object ScalaApp extends App { def formatted(context: String)(implicit deli: Delimiters): Unit = { println(deli.left + context + deli.right) } formatted("this is context") // 輸出: (this is context) }
def smaller[T] (a: T, b: T) = if (a < b) a else b
在Scala中若是定義了一個如上所示的比較對象大小的泛型方法,你會發現沒法經過編譯。對於對象之間進行大小比較,Scala和Java同樣,都要求被比較的對象須要實現java.lang.Comparable接口。在Scala中,直接繼承Java中Comparable接口的是特質Ordered,它在繼承compareTo方法的基礎上,額外定義了關係符方法,源碼以下:
trait Ordered[A] extends Any with java.lang.Comparable[A] { def compare(that: A): Int def < (that: A): Boolean = (this compare that) < 0 def > (that: A): Boolean = (this compare that) > 0 def <= (that: A): Boolean = (this compare that) <= 0 def >= (that: A): Boolean = (this compare that) >= 0 def compareTo(that: A): Int = compare(that) }
因此要想在泛型中解決這個問題,有兩種方法:
object Pair extends App { // 視圖界定 def smaller[T<% Ordered[T]](a: T, b: T) = if (a < b) a else b println(smaller(1,2)) //輸出 1 }
視圖限定限制了T能夠經過隱式轉換Ordered[T]
,即對象必定能夠進行大小比較。在上面的代碼中smaller(1,2)
中參數1
和2
其實是經過定義在Predef
中的隱式轉換方法intWrapper
轉換爲RichInt
。
// Predef.scala @inline implicit def intWrapper(x: Int) = new runtime.RichInt(x)
爲何要這麼麻煩執行隱式轉換,緣由是Scala中的Int類型並不能直接進行比較,由於其沒有實現Ordered
特質,真正實現Ordered
特質的是RichInt
。
Scala2.11+後,視圖界定被標識爲廢棄,官方推薦使用類型限定來解決上面的問題,本質上就是使用隱式參數進行隱式轉換。
object Pair extends App { // order既是一個隱式參數也是一個隱式轉換,即若是a不存在 < 方法,則轉換爲order(a)<b def smaller[T](a: T, b: T)(implicit order: T => Ordered[T]) = if (a < b) a else b println(smaller(1,2)) //輸出 1 }
更多大數據系列文章能夠參見我的 GitHub 開源項目: 程序員大數據入門指南