Scala Implicit

序:前段時間用spray的時候對於http請求的參數以及response的結果用json格式的時候遇到了一個隱式轉換,又專門查了一下加深下印象。溫故知新果真又有收穫,要記住這一句:隱式轉換函數只在乎 輸入類型,返回類型。php

咱們常常在scala 源碼裏上看到相似implicit這個關鍵字。json

一開始不太理解,後來看了看,發現implicit這個隱式轉換是支撐scala的易用、容錯以及靈活語法的基礎。app

咱們常常使用的一些集合方法裏面就用到了implicit,好比:函數

def flatMap[S, That](f: T => GenTraversableOnce[S])(implicit bf: CanBuildFrom[Repr, S, That]): That = if (bf(repr).isCombiner) { ...... }

 

1. 隱式轉換函數的定義:

咱們在scala repl裏定義一個方法foo,接受一個string的參數,打印出messageui

 當咱們傳入字符串參數"2"的時候,輸出2this

 可是當傳入的類型是int而不是string的時候,出現類型不匹配異常。spa

 若是咱們想支持隱式轉換,將int轉化爲string,能夠定義一個隱式函數,def implicit intToString( i : Int) = i.toStringscala

 這裏注意一下這個隱式函數的輸入參數和返回值。code

 輸入參數:接受隱式轉換入參爲int類型對象

 返回值: 返回結果是string.

scala> def foo(msg : String) = println(msg)  
foo: (msg: String)Unit  
  
scala> foo("2")  
2  
  
scala> foo(3)  
<console>:9: error: type mismatch;  
 found   : Int(3)  
 required: String  
              foo(3)  
                  ^  
  
scala> implicit def intToString(i:Int) = i.toString  
warning: there were 1 feature warning(s); re-run with -feature for details  
intToString: (i: Int)String  
  
scala> foo(3)  
3
scala> def bar(msg : String) = println("aslo can use implicit function intToString...result is "+msg)  
bar: (msg: String)Unit
scala> bar(33)  
aslo can use implicit function intToString...result is 33

總結一下,個人理解是:隱式函數是在一個scop下面,給定一種輸入參數類型,自動轉換爲返回值類型的函數,和函數名,參數名無關。

這裏intToString隱式函數是做用於一個scop的,這個scop在當前是一個scala repl,超出這個做用域,將不會起到隱式轉換的效果。

爲何我會這麼定義隱式函數,下面我再定義一個相同的輸入類型爲int,和返回值類型爲string的隱式函數名爲int2str:

scala> implicit def int2str(o :Int) = o.toString  
warning: there were 1 feature warning(s); re-run with -feature for details  
int2str: (o: Int)String  
  
scala> bar(33)  
<console>:13: error: type mismatch;  
 found   : Int(33)  
 required: String  
Note that implicit conversions are not applicable because they are ambiguous:  
 both method intToString of type (i: Int)String  
 and method int2str of type (o: Int)String  
 are possible conversion functions from Int(33) to String  
              bar(33)  
                  ^


跑出了二義性的異常,說的是intToString和int2str這2個隱式函數都是能夠處理bar(33)的,編譯器不知道選擇哪一個了。證實了隱式函數和函數名,參數名無關,只和輸入參數與返回值有關。

2. 隱式函數的應用

咱們能夠隨便的打開scala函數的一些內置定義,好比咱們最經常使用的map函數中->符號,看起來很像php等語言。

但實際上->確實是一個ArrowAssoc類的方法,它位於scala源碼中的Predef.scala中。下面是這個類的定義:

final class ArrowAssoc[A](val __leftOfArrow: A) extends AnyVal {  
  // `__leftOfArrow` must be a public val to allow inlining. The val  
  // used to be called `x`, but now goes by `__leftOfArrow`, as that  
  // reduces the chances of a user's writing `foo.__leftOfArrow` and  
  // being confused why they get an ambiguous implicit conversion  
  // error. (`foo.x` used to produce this error since both  
  // any2Ensuring and any2ArrowAssoc pimped an `x` onto everything)  
  @deprecated("Use `__leftOfArrow` instead", "2.10.0")  
  def x = __leftOfArrow  
  
  @inline def -> [B](y: B): Tuple2[A, B] = Tuple2(__leftOfArrow, y)  
  def →[B](y: B): Tuple2[A, B] = ->(y)  
}  
@inline implicit def any2ArrowAssoc[A](x: A): ArrowAssoc[A] = new ArrowAssoc(x)


咱們看到def ->[B] (y :B)返回的實際上是一個Tuple2[A,B]類型。

咱們定義一個Map:

scala> val mp = Map(1->"game1",2->"game_2")  
mp: scala.collection.immutable.Map[Int,String] = Map(1 -> game1, 2 -> game_2)


這裏 1->"game1"實際上是1.->("game_1")的簡寫。理解這個原理以後,之前這麼寫Map的時候這個箭頭的問題老是寫錯,這下不再會寫錯了。

這裏怎麼能讓整數類型1能有->方法呢。

這裏其實any2ArrowAssoc隱式函數起做用了,這裏接受的參數[A]是泛型的,因此int也不例外。

調用的是:將整型的1 implicit轉換爲 ArrowAssoc(1)

看下構造方法,將1看成__leftOfArrow傳入。

->方法的真正實現是生產一個Tuple2類型的對象(__leftOfArrow,y ) 等價於(1, "game_id")

這就是一個典型的隱式轉換應用。

 

其它還有不少相似的隱式轉換,都在Predef.scala中:

例如:Int,Long,Double都是AnyVal的子類,這三個類型之間沒有繼承的關係,不能直接相互轉換。

在Java裏,咱們聲明Long的時候要在末尾加上一個L,來聲明它是long。

但在scala裏,咱們不須要考慮那麼多,只須要:

scala> val l:Long = 10  
l: Long = 10


這就是implicit函數作到的,這也是scala類型推斷的一部分,靈活,簡潔。

其實這裏調用是:

val l : Long = int2long(10)

最後的總結:

1. 記住隱式轉換函數的同一個scop中不能存在參數和返回值徹底相同的2個implicit函數。

2. 隱式轉換函數只在乎 輸入類型,返回類型。

3. 隱式轉換是scala的語法靈活和簡潔的重要組成部分。

相關文章
相關標籤/搜索