經過隱式轉換,程序員能夠在編寫Scala程序時故意漏掉一些信息,讓編譯器去嘗試在編譯期間自動推導出這些信息來,這種特性能夠極大的減小代碼量,忽略那些冗長,過於細節的代碼。html
隱式轉換是Scala的一大特性, 若是對其不是很瞭解, 在閱讀Spark代碼時候就會很迷糊,有人這樣問過我?git
RDD這個類沒有reduceByKey,groupByKey等函數啊,而且RDD的子類也沒有這些函數,可是好像PairRDDFunctions這個類裏面好像有這些函數 爲何我能夠在RDD調用這些函數呢?程序員
答案就是Scala的隱式轉換; 若是須要在RDD上調用這些函數,有兩個前置條件須要知足:github
參考SparkContext Object, 咱們發現其中有上10個xxToXx類型的函數:apache
implicit def intToIntWritable(i: Int) = new IntWritable(i) implicit def longToLongWritable(l: Long) = new LongWritable(l) implicit def floatToFloatWritable(f: Float) = new FloatWritable(f) implicit def rddToPairRDDFunctions[K, V](rdd: RDD[(K, V)]) (implicit kt: ClassTag[K], vt: ClassTag[V], ord: Ordering[K] = null) = { new PairRDDFunctions(rdd) }
這麼一組函數就是隱式轉換,其中rddToPairRDDFunctions,就是實現:隱式的將RDD[(K, V)]類型的rdd轉換爲PairRDDFunctions對象,從而能夠在原始的rdd對象上 調用reduceByKey之類的函數;類型隱式轉換是在須要的時候纔會觸發,若是我調用須要進行隱式轉換的函數,隱式轉換纔會進行,不然仍是傳統的RDD類型的對象;函數
還說一個弱智的話,這個轉換不是可逆的;除非你提供兩個隱式轉換函數; 這是你會說,爲何我執行reduceByKey之後,返回的仍是一個rdd對象呢? 這是由於reduceByKey函數 是PairRDDFunctions類型上面的函數,可是該函數會返回一個rdd對象,從而在用戶的角度沒法感知到PairRDDFunctions對象的存在,從而精簡了用戶的認識, 不知曉原理的用戶能夠把reduceByKey,groupByKey等函數當着rdd自己的函數oop
上面是對spark中應用到隱式類型轉換作了分析,下面我就隱式轉換進行總結;ui
從一個簡單例子出發,咱們定義一個函數接受一個字符串參數,並進行輸出spa
def func(msg:String) = println(msg)
這個函數在func("11")調用時候正常,可是在執行func(11)或func(1.1)時候就會報error: type mismatch的錯誤. 這個問題很好解決.net
上面兩個方法使用的是傳統JAVA思路,雖然均可以解決該問題,可是缺點是不夠簡潔;在充滿了語法糖的Scala中, 針對類型轉換提供了特有的implicit隱式轉化的功能;
隱式轉化是一個函數, 能夠針對一個變量在須要的時候,自動的進行類型轉換;針對上面的例子,咱們能夠定義intToString函數
implicit def intToString(i:Int)=i.toString
此時在調用func(11)時候, scala會自動針對11進行intToString函數的調用, 從而實現能夠在func函數已有的類型上提供了新的類型支持,這裏有幾點要說一下:
上面咱們看到的例子是將函數的參數從一個類型自動轉換爲一個類型的例子,在Scala中, 除了針對函數參數類型進行轉換之外,還能夠對函數的調用者的類型進行轉換.
好比A+B,上面咱們談到是針對B進行類型自動轉換, 其實能夠在A上作類型轉換,下面咱們拿一個例子來講明
class IntWritable(_value:Int){ def value = _value def +(that:IntWritable): IntWritable ={ new IntWritable(that.value + value) } } implicit def intToWritable(int:Int)= new IntWritable(int) new IntWritable(10) + 10
上面咱們首先定義了一個類:IntWritable, 併爲int提供了一個隱式類型轉換intToWritable, 從而可使得IntWritable的+函數在原先只接受IntWritable類型參數的基礎上, 接受一個Int類型的變量進行運算,即new IntWritable(10) + 10能夠正常運行
如今換一個角度將"new IntWritable(10) + 10" 換爲"10 + new IntWritable(10)"會是什麼結果呢?會報錯誤嗎?
按道理是應該報錯誤,首先一個Int內置類型的+函數,沒有IntWritable這個參數類型; 其次,咱們沒有針對IntWritable類型提供到Int的隱式轉換, 即沒有提供writableToInt的implicit函數.
可是結果是什麼?10 + new IntWritable(10)的是能夠正常運行的,並且整個表達的類型爲IntWritable,而不是Int, 即Int的10被intToWritable函數隱式函數轉換爲IntWritable類型;
結論:隱式轉換能夠針對函數參數類型和函數對象進行類型轉換; 如今問題來了,看下面的例子
implicit def intToWritable(int:Int)= new IntWritable(int) implicit def writableToInt(that:IntWritable)=that.value val result1 = new IntWritable(10) + 10 val result2 = 10 + new IntWritable(10)
在上面的IntWritable類的基礎上,咱們提供了兩個隱式類型轉換函數, 即Int和IntWritable之間的雙向轉換;這樣的狀況下result1和result2兩個變量的類型是什麼?
答案:result1的類型爲IntWritable, result2的類型Int;很好理解, result1中的Int類型的10被intToWritable隱式轉換爲IntWritable;而result2中的IntWritable(10)被writableToInt 隱式轉換爲Int類型;
你確定會問?result2中爲何不是像上面的例子同樣, 把Int類型的10隱式轉換爲IntWritable類型呢?緣由就是隱式轉換的優先級;
發生類型不匹配的函數調用時, scala會嘗試進行類型隱式轉換;首先優先進行函數參數的類型轉換,若是能夠轉換, 那麼就完成函數的執行; 不然嘗試去對函數調用對象的類型進行轉換; 若是兩個嘗試都失敗了,就會報方法不存在或者類型不匹配的錯誤;
OK, Scala的隱式轉換是Scala裏面隨處可見的語法, 在Spark中也很重要, 這裏對它的講解,算是對Shuffle作一個補充了, 即一個RDD之因此能夠進行基於Key的Shuffle操做 是由於RDD被隱式轉換爲PairRDDFunctions類型。
1.將方法或變量標記爲implicit
2.將方法的參數列表標記爲implicit
3.將類標記爲implicit
Scala支持兩種形式的隱式轉換:
隱式值:用於給方法提供參數
隱式視圖:用於類型間轉換或使針對某類型的方法能調用成功
例1:聲明person方法。其參數爲name,類型String
scala> def person(implicit name : String) = name //name爲隱式參數 person: (implicit name: String)String
直接調用person方法
scala> person <console>:9: error: could not find implicit value for parameter name: String person ^
scala> implicit val p = "mobin" //p被稱爲隱式值 p: String = mobin scala> person res1: String = mobin
scala> implicit val p1 = "mobin1" p1: String = mobin1 scala> person <console>:11: error: ambiguous implicit values: both value p of type => String and value p1 of type => String match expected type String person ^
匹配失敗,因此隱式轉換必須知足無歧義規則,在聲明隱式參數的類型是最好使用特別的或自定義的數據類型,不要使用Int,String這些經常使用類型,避免碰巧匹配
例2:將整數轉換成字符串類型:
scala> def foo(msg : String) = println(msg) foo: (msg: String)Unit scala> foo(10) <console>:11: error: type mismatch; found : Int(10) required: String foo(10) ^
顯然不能轉換成功,解決辦法就是定義一個轉換函數給編譯器將int自動轉換成String
scala> implicit def intToString(x : Int) = x.toString intToString: (x: Int)String scala> foo(10) 10
例3:經過隱式轉換,使對象能調用類中本不存在的方法
class SwingType{ def wantLearned(sw : String) = println("兔子已經學會了"+sw) } object swimming{ implicit def learningType(s : AminalType) = new SwingType } class AminalType object AminalType extends App{ import com.mobin.scala.Scalaimplicit.swimming._ val rabbit = new AminalType rabbit.wantLearned("breaststroke") //蛙泳 }
class SwingType{ def wantLearned(sw : String) = println("兔子已經學會了"+sw) } package swimmingPage{ object swimming{ implicit def learningType(s : AminalType) = new SwingType //將轉換函數定義在包中 } } class AminalType object AminalType extends App{ import com.mobin.scala.Scalaimplicit.swimmingPage.swimming._ //使用時顯示的導入 val rabbit = new AminalType rabbit.wantLearned("breaststroke") //蛙泳 }
像intToString,learningType這類的方法就是隱式視圖,一般爲Int => String的視圖,定義的格式以下:
implicit def originalToTarget (<argument> : OriginalType) : TargetType
其一般用在於以兩種場合中:
1.若是表達式不符合編譯器要求的類型,編譯器就會在做用域範圍內查找可以使之符合要求的隱式視圖。如例2,當要傳一個整數類型給要求是字符串類型參數的方法時,在做用域裏就必須存在Int => String的隱式視圖
2.給定一個選擇e.t,若是e的類型裏並無成員t,則編譯器會查找能應用到e類型而且返回類型包含成員t的隱式視圖。如例3
在scala2.10後提供了隱式類,可使用implicit聲明類,可是須要注意如下幾點:
1.其所帶的構造參數有且只能有一個
2.隱式類必須被定義在類,伴生對象和包對象裏
3.隱式類不能是case class(case class在定義會自動生成伴生對象與2矛盾)
4.做用域內不能有與之相同名稱的標示符
object Stringutils { implicit class StringImprovement(val s : String){ //隱式類 def increment = s.map(x => (x +1).toChar) } } object Main extends App{ import com.mobin.scala.implicitPackage.Stringutils._ println("mobin".increment) }
編譯器在mobin對象調用increment時發現對象上並無increment方法,此時編譯器就會在做用域範圍內搜索隱式實體,發現有符合的隱式類能夠用來轉換成帶有increment方法的StringImprovement類,最終調用increment方法。
1.當方法中的參數的類型與目標類型不一致時
2.當對象調用類中不存在的方法或成員時,編譯器會自動將對象進行隱式轉換
即編譯器是如何查找到缺失信息的,解析具備如下兩種規則:
1.不存在二義性(如例1)
2.隱式操做不能嵌套使用,即一次編譯只隱式轉換一次(One-at-a-time Rule)
Scala不會把 x + y 轉換成 convert1(convert2(x)) + y
- https://github.com/ColZer/DigAndBuried/blob/master/spark/scala-implicit.md
- https://blog.csdn.net/jameshadoop/article/details/52337949
- https://www.cnblogs.com/MOBIN/p/5351900.html