1. 程式人生 > >【圖文詳細 】Scala——隱式轉換和隱式引數

【圖文詳細 】Scala——隱式轉換和隱式引數

2、Scala 隱式轉換和隱式引數 

隱式轉換和隱式引數是 Scala 中兩個非常強大的功能,利用隱式轉換和隱式引數,你可以提 供優雅的類庫,

對類庫的使用者隱匿掉那些枯燥乏味的細節。 
 
隱式的對類的方法進行增強,豐富現有類庫的功能 
 
是指那種以 implicit 關鍵字宣告的帶有單個引數的函式。

可以通過::implicit -v 這個命令顯示所有做隱式轉換的類。 

 

2.1、Scala 隱式轉換探討 

現在我們來考慮一個問題: 之前講過:

1 to 10 其實可以寫成 1.to(10)

那其實就是表示:1 是一個 Int 型別的變數,所以證明 Int 類中會有一個 to 的方法 但事實上,我們在 Int 型別中根本就沒有尋找 to 方法 那也就是說對一個 Int 型別的變數 1 呼叫 Int 型別不存在的一個方法,這怎麼還能正常執行 呢?

原因就是隱式轉換 
 
看一個最簡單的隱式轉換的例子: 

那我們首先來看一下隱式引數: 

package com.mazh.scala.day3 
 
object ImplicitParamTest { 
  // 正常的普通方法 
  def add(x:Int, y:Int) = x + y 
 
  // 柯里化的方法 
  def add2(x:Int)(y:Int) = x + y 
  def add3(x:Int)(y:Int = 10) = x + y 
 
//  如果變成下面這種形式: 
  def add4(x:Int)(implicit y:Int = 10) = x + y 
 
  def main(args: Array[String]): Unit = { 
 
    println ( add (2,3)) 
    // 不能只傳一個引數取使用,必須要傳入兩個引數 
    println ( add2 (2)(3)) 
    println ( add 3 (2)()) 
    // 呼叫帶隱式引數的函式 
    println ( add 4 (2)) 
  } 
}

在上面的程式碼中,可以看出來,如果對 add2 方法的第二個引數,做了隱式宣告,發現之前 需要傳入兩個引數才能執行的方法 add2 就可以只傳入一個引數就能執行計算 
 
那有什麼應用場景呢? 比如匯率計算!!!!! 

object ImplicitParamTest2 { 
  /** 
    *  第一個引數是要換算成美元的人民幣數目 
    *  第二個引數是匯率 
    */ 
  def rmb(dollar:Double)(implicit rate:Double = 6) = dollar * rate 
 
  def main(args: Array[String]): Unit = { 
 
    println ( rmb (100)) 
    println ( rmb (100)(7)) 
 // 引入隱式轉換值,所以第二個引數被隱式的轉換成了 6.66 
    import MyPredef._ 
    println ( rmb (100)) 
  } 
} 
 
object MyPredef{ 
  // 宣告一個 Double 型別的隱式轉換值 
  implicit var current_rate :Double = 6.66 
}

總結:

1、 隱式轉換會首先從全域性中尋找,尋找不到,才使用隱式引數

2、 隱式轉換隻能定義在 object 中

3、 如果隱式轉換存在二義性,那麼程式會跑錯 
 
那現在再來考慮:

對一個 Int 型別的變數 1 呼叫 Int 型別不存在的一個方法,程式能正常執行得到期待的結果,

沒有拋錯異常,到底是怎麼回事?會不會就是 Int 型別的變數被隱式轉換成了另一種包含 to 方法的型別了呢? 
 
1、首先,我們在 RichInt 中發現了 to 方法: 

2、檢視系統是否為我們自動引入了預設的各種隱式轉換: 在 Scala 互動命令列中執行命令:implicits -v 
 

scala> :implicit -v 
   /* 69 implicit members imported from scala.Predef */
   /* 7 inherited from scala */
   final implicit class ArrayCharSequence extends CharSequence 
   final implicit class ArrowAssoc[A] extends AnyVal
   final implicit class Ensuring[A] extends AnyVal
   final implicit class RichException extends AnyVal
   final implicit class SeqCharSequence extends CharSequence
   final implicit class StringFormat[A] extends AnyVal
   final implicit class any2stringadd[A] extends AnyVal 
 
   /* 40 inherited from scala.Predef */
   implicit def ArrowAssoc[A](self: A): ArrowAssoc[A]
   implicit def Ensuring[A](self: A): Ensuring[A]
   implicit def StringFormat[A](self: A): StringFormat[A]
   implicit def any2stringadd[A](self: A): any2stringadd[A] 
 
   implicit def booleanArrayOps(xs: Array[Boolean]): mutable.ArrayOps[Boolean]
   implicit def byteArrayOps(xs: Array[Byte]): mutable.ArrayOps[Byte]
   implicit def charArrayOps(xs: Array[Char]): mutable.ArrayOps[Char]
   implicit def doubleArrayOps(xs: Array[Double]): mutable.ArrayOps[Double]
   implicit def floatArrayOps(xs: Array[Float]): mutable.ArrayOps[Float]
   implicit def genericArrayOps[T](xs: Array[T]): mutable.ArrayOps[T]
   implicit def intArrayOps(xs: Array[Int]): mutable.ArrayOps[Int]
   implicit def longArrayOps(xs: Array[Long]): mutable.ArrayOps[Long]
   implicit def refArrayOps[T <: AnyRef](xs: Array[T]): mutable.ArrayOps[T]
   implicit def shortArrayOps(xs: Array[Short]): mutable.ArrayOps[Short]
   implicit def unitArrayOps(xs: Array[Unit]): mutable.ArrayOps[Unit] 
 
   implicit def $conforms[A]: <:<[A,A]
   implicit def ArrayCharSequence(__arrayOfChars: Array[Char]): ArrayCharSequence   
   implicit def Boolean2boolean(x: Boolean): Boolean
   implicit def Byte2byte(x: Byte): Byte
   implicit def Character2char(x: Character): Char
   implicit def Double2double(x: Double): Double
   implicit def Float2float(x: Float): Float
   implicit def Integer2int(x: Integer): Int
   implicit def Long2long(x: Long): Long
   implicit def RichException(self: Throwable): RichException
   implicit def SeqCharSequence(__sequenceOfChars: IndexedSeq[Char]): SeqCharSequence   
   implicit def Short2short(x: Short): Short
   implicit val StringCanBuildFrom: generic.CanBuildFrom[String,Char,String]
   implicit def augmentString(x: String): immutable.StringOps
   implicit def boolean2Boolean(x: Boolean): Boolean
   implicit def byte2Byte(x: Byte): Byte
   implicit def char2Character(x: Char): Character
   implicit def double2Double(x: Double): Double
   implicit def float2Float(x: Float): Float 
   implicit def int2Integer(x: Int): Integer
   implicit def long2Long(x: Long): Long
   implicit def short2Short(x: Short): Short
   implicit def tuple2ToZippedOps[T1, T2](x: (T1, T2)): runtime.Tuple2Zipped.Ops[T1,T2]   
   implicit def tuple3ToZippedOps[T1, T2, T3](x: (T1, T2, T3)):runtime.Tuple3Zipped.Ops[T1,T2,T3]
   implicit def unaugmentString(x: immutable.StringOps): String 
 
   /* 22 inherited from scala.LowPriorityImplicits */
   implicit def genericWrapArray[T](xs: Array[T]): mutable.WrappedArray[T]
   implicit def wrapBooleanArray(xs: Array[Boolean]): mutable.WrappedArray[Boolean]   
   implicit def wrapByteArray(xs: Array[Byte]): mutable.WrappedArray[Byte]
   implicit def wrapCharArray(xs: Array[Char]): mutable.WrappedArray[Char]
   implicit def wrapDoubleArray(xs: Array[Double]): mutable.WrappedArray[Double]   
   implicit def wrapFloatArray(xs: Array[Float]): mutable.WrappedArray[Float]
   implicit def wrapIntArray(xs: Array[Int]): mutable.WrappedArray[Int]
   implicit def wrapLongArray(xs: Array[Long]): mutable.WrappedArray[Long]
   implicit def wrapRefArray[T <: AnyRef](xs: Array[T]): mutable.WrappedArray[T]   
   implicit def wrapShortArray(xs: Array[Short]): mutable.WrappedArray[Short]
   implicit def wrapUnitArray(xs: Array[Unit]): mutable.WrappedArray[Unit] 
 
   implicit def booleanWrapper(x: Boolean): runtime.RichBoolean
   implicit def byteWrapper(x: Byte): runtime.RichByte
   implicit def charWrapper(c: Char): runtime.RichChar
   implicit def doubleWrapper(x: Double): runtime.RichDouble
   implicit def floatWrapper(x: Float): runtime.RichFloat
   implicit def intWrapper(x: Int): runtime.RichInt
   implicit def longWrapper(x: Long): runtime.RichLong
   implicit def shortWrapper(x: Short): runtime.RichShort
   implicit def unwrapString(ws: immutable.WrappedString): String
   implicit def wrapString(s: String): immutable.WrappedString 

通過觀察發現,scala 會預設給我們引入 scala 中的 Predef.scala 中的所有隱式轉換 

https://www.scala-lang.org/api/2.11.8/#scala.Predef$

最後在倒數第五行發現,有一個隱式方法能夠把 Int 型別的變數轉換成 runtime.RichInt 變數 符合我們的預期 

最終解釋:

當呼叫了:1 to 10

其實是呼叫了:1.to(10)

但是:Int 中沒有 to 方法 所以:去尋找引入的隱式轉換中有沒有能把 Int 型別轉換成能執行 to 方法的型別

果然:在系統引入的轉換中發現:implicit def intWrapper(x: Int): runtime.RichInt

所以:最終 int 型別的 1 就被轉換成了 RichInt 型別的變數 

驗證:RichInt 中確實存在 to 方法

最後:順理成章的呼叫 RichInt(1).to(10)生成返回結果

結論:神奇但又合理 

 

2.2、隱式轉換的發生時機  

到底在什麼時候觸發隱式轉換呢? 

 

2.2.1、時機一:當呼叫某個物件不存在的方法時 

當一個物件去呼叫某個方法,但是這個物件並不具備這個方法。這個時候會觸發隱式轉換, 會把這個物件(偷偷的)隱式轉換為具有這個方法的那個物件。這就和剛才解釋的為什麼 Int 型別沒有 to 方法還是能夠呼叫 to 方法,因為 Int 型別的變數 1 在呼叫 to 方法的時候,被隱 式轉換成了 RichInt 的物件 

再次演示一個案例: 

object ImplicitTest2 { 
  def main(args: Array[String]): Unit = { 
    import FileImplicit._ 
    val file = new File("c:\\words.txt") 
    // file 物件是沒有 readAll 方法的, 那麼在呼叫一個不存在的方法的時候 
    // scala 會檢視是否有隱式轉換能把 file 物件轉換成具有 readAll 方法物件 
    val allText = file.readAll() 
    println (allText) 
  } 
} 
 
class RichFile(f:File){ 
  def readAll():String = { 
    Source. fromFile (f).mkString 
  } 
} 
 
object FileImplicit{ 
 implicit def file2RichFile(f:File):RichFile = new RichFile(f) 
} 
 

2.2.2、時機二:當方法引數型別不匹配時 

正常情況下,我們在編寫程式碼,如果去呼叫某個方法,確實這個方法也存在,要是傳入的參 數型別不匹配,程式會拋錯,這是再正常不過的事情了。 
 
可是 Scala 卻在為我們做努力,努力幫助我們把這個方法呼叫執行起來。因為隱式轉換的存 在 第一個案例:

object Demo009_Implicit_Type { 
 
  def main(args: Array[String]): Unit = { 
 
    // 定義一個隱式轉換,能夠、把一個 double型別的數程式設計 int型別。 
    implicit def double2Int(a:Double) = a.toInt 
 
    // 定義三個方法
    def sum1(x:Int, y:Int) = x + y 
    def sum2(x:Int, y:Double) = x + y 
    def sum3(x:Double, y:Double) = x + y 
 
    // 使用
     println(sum1(1, 2.0))     // 觸發隱式轉換
     println(sum2(1, 2))       // 觸發隱式轉換
     println(sum3(1,2))        // 觸發隱式轉換
   } 
} 

第二個案例: 

package com.mazh.scala.day3 
 
// 特殊人群 
class SpecialPerson(var name:String) 
// 特殊人群之一 
class Young(name:String) 
// 特殊人群之二 
class Older(name:String) 
// 正常人群之一 
class Worker(var name:String) 
// 正常人群之二 
class Adult(var name:String) 
 
class TicketHouse{ 
  def buyTicket(p:SpecialPerson): Unit ={ 
    println (p.name+"票給你!!爽去吧!!"); 
  } 
} 
object ObjectImplicit{ 
  implicit def object2special(obj:AneyRef):SpecialPerson={ 
// 這麼 寫的原因是給大家演示這三個 方法 的使用。其實 有 更簡單的實現
 
    if(obj.getClass == classOf [Young]){ 
      val young = obj.asInstanceOf[Young] 
      new SpecialPerson(young.name) 
    }else if(obj.getClass == classOf [Older]){ 
      val older = obj.asInstanceOf[Older] 
      new SpecialPerson(older.name) 
    }else{ 
      new SpecialPerson("NULL") 
    } 
  } 
} 
object ImplicitTest3 { 
  def main(args: Array[String]): Unit = { 
    val ticketHouse = new TicketHouse() 
    val young = new Young("Young") 
    val older = new Older("Older") 
    val worker = new Worker("Worker") 
    val adult = new Worker("Adult") 
 
    import ObjectImplicit._ 
//  ticketHouse.buyTicke t(worker)    // 報錯 
//  ticketHouse.buyTicket(adult)     // 報錯 
    ticketHouse.buyTicket(young) 
    ticketHouse.buyTicket(older) 
  } 
} 

 

2.3、隱式轉換忠告 

下面給出我自己開發實踐中的部分總結,供大家參考:

 1、即使你能輕鬆駕馭 Scala 語言中的隱式轉換,能不用隱式轉換就儘量不用

 2、如果一定要用,在涉及多次隱式轉換時,必須要說服自己這樣做的合理

 3、如果只是炫耀自己的 Scala 程式設計能力,請大膽使用