天天看點

scala implicit 隐式轉換

我們經常在scala 源碼裡上看到類似implicit這個關鍵字。

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

我們經常使用的一些集合方法裡面就用到了implicit,比如:

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

我們在scala repl裡定義一個方法foo,接受一個string的參數,列印出message

 當我們傳入字元串參數"2"的時候,輸出2

 但是當傳入的類型是int而不是string的時候,出現類型不比對異常。

 如果我們想支援隐式轉換,将int轉化為string,可以定義一個隐式函數,def implicit intToString( i : Int) = i.toString

 這裡注意一下這個隐式函數的輸入參數和傳回值。

 輸入參數:接受隐式轉換入參為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")的簡寫。

這裡怎麼能讓整數類型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的文法靈活和簡潔的重要組成部分。

原創,轉載請注明出自:http://blog.csdn.net/oopsoom/article/details/24643869

-EOF-