Implict conversion
隱式定義是編譯器為了修正類型錯(cuò)誤而添加的定義,通過在程序中添加隱式定義,自動(dòng)進(jìn)行類型轉(zhuǎn)換
scala.Predef中scala.Int轉(zhuǎn)換為java.lang.Integer的隱式轉(zhuǎn)換
implicit def int2Integer(x: Int) = java.lang.Integer.valueOf(x)
隱式轉(zhuǎn)換規(guī)則:
- 標(biāo)記:必須用
implicit標(biāo)記
implicit def intToString(x: Int) = x.toString
- 作用域:只使用作用域內(nèi)部的隱式轉(zhuǎn)換,轉(zhuǎn)換方法作為單一標(biāo)識(shí)符,或者是位于伴生對(duì)象中
單一標(biāo)識(shí)符single identifier:假設(shè)一個(gè)轉(zhuǎn)換方法
convert(x:Int),不能用someVariable.convert(x)調(diào)用,而是可以直接使用convert(x)調(diào)用
- 無歧義:只能存在一種隱式轉(zhuǎn)換,不能沖突
- 顯式優(yōu)先:如果類型檢查無誤,不會(huì)嘗試進(jìn)行隱式操作
用途
- 隱式轉(zhuǎn)換為期望類型,例如
Int轉(zhuǎn)Double
implicit def int2double(x: Int): Double = x.toDouble
- 轉(zhuǎn)換方法調(diào)用對(duì)象的類型
與新的類型交互操作
class Rational(n: Int, d: Int) {
...
def + (that: Rational): Rational = ...
def + (that: Int): Rational = ...
}
scala> val oneHalf = new Rational(1, 2)
oneHalf: Rational = 1/2
scala> implicit def intToRational(x: Int) = new Rational(x, 1)
intToRational: (x: Int)Rational
scala> 1 + oneHalf
res2: Rational = 3/2
- 模擬新的語法
Map(1 -> "one", 2 -> "two", 3 -> "three")中的箭頭語法定義,位于Predef.scala中
implicit final class ArrowAssoc[A](private val self: A) extends AnyVal {
@inline def -> [B](y: B): Tuple2[A, B] = Tuple2(self, y)
def →[B](y: B): Tuple2[A, B] = ->(y)
}
- 隱式類
編譯器自動(dòng)生成隱式轉(zhuǎn)換,從隱式類的參數(shù)轉(zhuǎn)換為對(duì)應(yīng)的類本身
隱式類不能是一個(gè)樣本類,它的構(gòu)造函數(shù)只能有一個(gè)參數(shù),同時(shí)必須位于其他對(duì)象,類或特質(zhì)中
case class Rectangle(width: Int, height: Int)
//定義隱式類
implicit class RectangleMaker(width: Int) {
def x(height: Int) = Rectangle(width, height)
}
// 編譯器自動(dòng)生成一個(gè)隱式方法,從構(gòu)造函數(shù)轉(zhuǎn)換為隱式類
implicit def RectangleMaker(width: Int) = new RectangleMaker(width)
//使用
scala> val myRectangle = 3 x 4
myRectangle: Rectangle = Rectangle(3,4)
- 隱式對(duì)象
形如implicit object,例如scala.math.Ordering的伴生對(duì)象中定義了眾多的隱式對(duì)象
trait IntOrdering extends Ordering[Int] {
def compare(x: Int, y: Int) = java.lang.Integer.compare(x, y)
}
implicit object Int extends IntOrdering
當(dāng)需要隱式參數(shù)(implict ordering:Ordering[Int])時(shí),編譯器傳遞給它的就是scala.math.Ordering.Int對(duì)象
Implicit parameter
方法可以有一個(gè)隱式參數(shù)列表,在參數(shù)列表的開始處由implicit關(guān)鍵字標(biāo)記,如果隱式參數(shù)列表省略,會(huì)自動(dòng)在作用域范圍內(nèi)尋找對(duì)應(yīng)類型的隱式變量
隱式參數(shù)最常見的用法是提供顯示參數(shù)列表的一些類型信息,比如Ordering,ClassTag
def maxListImpParm[T](elements: List[T])
(implicit ordering: Ordering[T]): T =
elements match {
case List() =>
throw new IllegalArgumentException("empty list!")
case List(x) => x
case x :: rest =>
val maxRest = maxListImpParm(rest)(ordering)//ordering可以省略,編譯器可以用方法的隱式參數(shù)填充
if (ordering.gt(x, maxRest)) x
else maxRest
}
隱式參數(shù)的類型最好使用自定義名稱的類型,以免沖突,例如不要直接使用隱式類型為(T,T)=>Boolen函數(shù),容易影響到其他的函數(shù)
標(biāo)準(zhǔn)庫Predef中有如下函數(shù)
@inline def implicitly[T](implicit e: T) = e
所以可以在方法體內(nèi)部不使用隱式參數(shù)的名稱,通過implicitly[Ordering[T]]獲得類型為Ordering[T]的隱式定義
def maxList[T](elements: List[T])
(implicit ordering: Ordering[T]): T =
elements match {
case List() =>
throw new IllegalArgumentException("empty list!")
case List(x) => x
case x :: rest =>
val maxRest = maxList(rest)
if (implicitly[Ordering[T]].gt(x, maxRest)) x
else maxRest
}
視界 Context Bound
進(jìn)一步省略形參中的參數(shù)名
def maxList[T : Ordering](elements: List[T]): T =
elements match {
case List() =>
throw new IllegalArgumentException("empty list!")
case List(x) => x
case x :: rest =>
val maxRest = maxList(rest)
if (implicitly[Ordering[T]].gt(x, maxRest)) x
else maxRest
}
使用視界(context bound)語法[T: Bound]:表明類型參數(shù)T,隱含參數(shù)Bound[T]
def g[A : B](a: A) = h(a)
//等價(jià)于
def g[A](a: A)(implicit ev: B[A]) = h(a) //h需要一個(gè)隱式類型為B[A]的隱式值
Scala數(shù)組是泛型,會(huì)進(jìn)行類型擦除,為了在運(yùn)行時(shí)獲取類型信息,需要ClassTag
scala> def mkArray[T : ClassTag](elems: T*) = Array[T](elems: _*)
mkArray: [T](elems: T*)(implicit evidence$1: scala.reflect.ClassTag[T])Array[T]
scala> mkArray(42, 13)
res0: Array[Int] = Array(42, 13)
scala> mkArray("Japan","Brazil","Germany")
res1: Array[String] = Array(Japan, Brazil, Germany)
//內(nèi)部實(shí)際調(diào)用
mkArray[String]("Japan", "Brazil", "Germany")((ClassTag.apply[String](classOf[java.lang.String]): scala.reflect.ClassTag[String]));
scala.reflect.ClassTag通過runtimeClass字段獲取運(yùn)行時(shí)類型信息
視界與類型參數(shù)的上界含義完全不同,
[T <: Bound[T]]指定T是Bound[T]的子類型
支持形如[K : Ordering : ClassTag]的多個(gè)隱式參數(shù)的用法,其含義為泛型類型K,隱式參數(shù)scala.reflect.ClassTag[K]和Ordering[K]
多個(gè)隱式轉(zhuǎn)換的選擇
多個(gè)隱式轉(zhuǎn)換都可行的時(shí)候,優(yōu)先選擇更具體的隱式轉(zhuǎn)換
- 如果前者的參數(shù)類型是后者的子類型,選前者,例如字符串優(yōu)先選
String而不是Any類型的參數(shù) - 位于子類中的方法優(yōu)先于父類中的方法,例如遇到
"abc".reverse方法時(shí),優(yōu)先使用的是轉(zhuǎn)換為StringOps的隱式轉(zhuǎn)換
//Predet中的方法
@inline implicit def augmentString(x: String): StringOps = new StringOps(x)
//Predef繼承的LowPriorityImplicits類中的隱式轉(zhuǎn)換
implicit def wrapString(s: String): WrappedString = if (s ne null) new WrappedString(s) else null
使用
scalac -Xprint:typer xxx.scala可以查看具體的類型