【圖文詳細 】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 程式設計能力,請大膽使用