Scala 隱式轉(zhuǎn)換和隱式參數(shù)

Implict conversion

隱式定義是編譯器為了修正類型錯(cuò)誤而添加的定義,通過在程序中添加隱式定義,自動(dòng)進(jìn)行類型轉(zhuǎn)換

scala.Predefscala.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 可以查看具體的類型

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容