先來看看Spark裏關於implicit的實現,如下:
object implicits extends SQLImplicits with Serializable {
protected override def _sqlContext: SQLContext = self
implicit class StringToColumn(val sc: StringContext) {
def $(args: Any*): ColumnName = {
new ColumnName(sc.s(args: _*))
}
}
}
其中 SQLImplicits
是一個抽象類,包含了一系列的隱式轉換,不過主要負責將scala對象轉換爲DataFrame。我們主要用到的是$
符,涉及到的是StringToColumn
這個隱式類裏的$
方法,所以重點看這個方法。
一開始以爲它只是個普通的隱式類,我們知道隱式類一般來說這麼用
object Implicits {
implicit class RangeMaker(val start: Int) {
def -->(end: Int) = start to end
}
}
// 接下來調用如下
import Implicits._
1 --> 10 mkString(",") => 1,2,3,4,5,6,7,8,9,10
這裏隱式類RangeMaker
相當於給Int
類型添加了一個-->
方法,所以1 --> 10
實際是new RangeMaker(1).-->(10)
的調用。
但是我們看Spark
裏的隱式方法調用,它是df.select($"columnA", $"columnB")
,我們發現$"xx"
沒法轉成new StringToColumn(xx).$(xx)
調用方式,那它是怎麼工作的呢?
我們照它仿寫一個例子:
object Implicits { implicit class Test(val a: Int) { def $(b: Int) = b }}
// 調用測試
import Implicits._
$12 => error: not found: value $12, $(12) => error: not found: value $
我們會發現,這種調用方式是不成功的,因爲它根本是錯的。所以我又懷疑是Spark相應類裏有上下文,結果找了很久也沒什麼發現。所以,說明特殊性必是與StringContext
這個類有關係,查了下發現它是個字符串插值類,進去看了才發現問題果然出在這。
它是
case class StringContext(parts: String*) { ... }
,提供了s
,f
,raw
三種字符串插值方式,我們常用的是s"xxx$xx"
,這裏會將變量xx
進行替換。而f
則可以進行字符串的簡單格式化,如f"$name%s is $height%2.2f"
。raw
插值器則跟s
插值器差不多,不過它不會轉\n
。
而對於
s"xxx$xx"
它的調用方式實際是new StringContext("xxx").s(xx)
,這個我們在類裏面並沒有找到,因爲它是scala編譯器
做了特殊處理的,StringContext
類是由內核負責處理的特殊類,它只放出來了部分插接點來讓我們做擴展。可參見SIP文檔
Spark裏的這個隱式類,實際是給StringContext
類擴展了一個$
方法,所以當我們調用$"columnA"
的時候,實際是new StringToColumn(new StringContext("columnA")).$()
,這些都是編譯器做的處理!
來個小測試,自己來擴展StringContext
的方法,代碼如下:
object Implicits { implicit class Test(val sc: StringContext) extends AnyVal { def back(args: Any*) = args }}
// 調用
import Implicits._
back"123" => List()
val name = 11; back"name: $name" => WrappedArray(11)
val name = 11; val id = 12; back"name: $name, id: $id" => WrappedArray(11, 12)
這裏back方法是把傳入的參數原樣輸出,back"123"
實際的調用是new Test(new StringContext("123")).back()
,所以輸出爲空,與Spark裏常用的$"columnA"
一致。val name = 11; back"name: $name"
實際的調用是new Test(new StringContext("name: ")).back(11)
,所以輸出的是WrappedArray(11)
。對於val name = 11; val id = 12; back"name: $name, id: $id"
實際調用的是new Test(new StringContext("name: ", ", id: ")).back(11, 12)
,所以輸出的是WrappedArray(11, 12)
。
由此可以看出,對於back"name: $name, id: $id"
,StringContext
類的方法邏輯是先拆分裏面的變量,用"name: ", ", id: "
來初始化StringContext
類,賦給parts
成員變量,然後再把參數裏的變量替換爲實際值賦給方法。
歡迎轉載,但請註明出處:https://my.oschina.net/u/2539801/blog/776027