Scala implicit
implicit基本含義
在Scala中有一個關鍵字是implicit
, 之前一直不知道這個貨是干什么的,今天整理了一下。
我們先來看一個例子:
def display(input:String):Unit = println(input)
我們可以看到,display
函數的定義只是接受String
類型的入參,因此調用display("any string")
這樣的函數是沒問題的。但是如果調用display(1)
這樣的入參不是String類型的話,編譯會出錯的。
如果我們想讓display函數也能夠支持Int類型的入參的話,除了我們重新定一個def display(input:Int):Unit = println(input)
這樣的函數以外,我們還可以在相同的作用域內用implicit
關鍵字定義一個隱式轉換函數,示例代碼如下:
object ImplicitDemo {
def display(input:String):Unit = println(input)
implicit def typeConvertor(input:Int):String = input.toString
implicit def typeConvertor(input:Boolean):String = if(input) "true" else "false"
// implicit def booleanTypeConvertor(input:Boolean):String = if(input) "true" else "false"
def main(args: Array[String]): Unit = {
display("1212")
display(12)
display(true)
}
}
我們定義了2個隱式轉換函數:
implicit def typeConvertor(input:Int):String = input.toString
implicit def typeConvertor(input:Boolean):String = if(input) "true" else "false"
這樣display
函數就可以接受String、Int、Boolean類型的入參了。注意到上面我們的例子中注釋的那一行,如果去掉注釋的那一行的話,會在運行的時候出現二義性:
Error:(18, 13) type mismatch;
found : Boolean(true)
required: String
Note that implicit conversions are not applicable because they are ambiguous:
both method typeConvertor in object ImplicitDemo of type (input: Boolean)String
and method booleanTypeConvertor in object ImplicitDemo of type (input: Boolean)String
are possible conversion functions from Boolean(true) to String
display(true)
^
得出的結論是:
隱式轉換函數是指在同一個作用域下面,一個給定輸入類型並自動轉換為指定返回類型的函數,這個函數和函數名字無關,和入參名字無關,只和入參類型以及返回類型有關。注意是同一個作用域。
implicit的應用
我們可以隨便的打開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)
更牛逼的功能
為現有的類庫增加功能的一種方式,用java的話,只能用工具類或者繼承的方式來實現,而在scala則還可以采用隱式轉化的方式來實現。
隱式參數
看一個例子再說:
object ImplictDemo {
object Context{
implicit val ccc:String = "implicit"
}
object Param{
def print(content:String)(implicit prefix:String){
println(prefix+":"+content)
}
}
def main(args: Array[String]) {
Param.print("jack")("hello")
import Context._
Param.print("jack")
}
}
程序運行結果為:
hello:jack
implicit:jack
隱式轉換擴展
import java.io.File
import scala.io.Source
class RichFile(val file:File){
def read = Source.fromFile(file.getPath()).mkString
}
object Context{
implicit def file2RichFile(f:File)= new RichFile(f)
}
object ImplictDemo {
def main(args: Array[String]) {
import Context.file2RichFile
println(new File("f:\\create.sql").read)
}
}
上面的代碼中調用的read方法其實就是RichFile中定義的read方法。
最后的總結:
- 記住隱式轉換函數的同一個scop中不能存在參數和返回值完全相同的2個implicit函數。
- 隱式轉換函數只在意 輸入類型,返回類型。
- 隱式轉換是scala的語法靈活和簡潔的重要組成部分