Scala入門到精通——第十九節 隱式轉換與隱式引數(二)
本節主要內容
- 隱式引數中的隱式轉換
- 函式中隱式引數使用概要
- 隱式轉換問題梳理
1. 隱式引數中的隱式轉換
前一講中,我們提到函式中如果存在隱式引數,在使用該函式的時候如果不給定對應的引數,則編譯器會自動幫我們搜尋相應的隱式值,並將該隱式值作為函式的引數,這裡面其實沒有涉及到隱式轉換,本節將演示如何利用隱式引數進行隱式轉換,下面的程式碼給定的是一個普通的比較函式:
object ImplicitParameter extends App {
//下面的程式碼不能編譯通過
//這裡面泛型T沒有具體指定,它不能直接使用
//<符號進行比較
def compare[T](first:T,second:T)={
if (first < second)
first
else
second
}
}
上面的程式碼要想使其編譯通過,可以前型別變數界定和檢視界定指定其上界為Ordered[T],例如:
object ImplicitParameter extends App {
//指定T的上界為Ordered[T],所有混入了特質Ordered
//的類都可以直接的使用<比較符號進行比較
def compare[T<:Ordered[T]](first:T,second:T)={
if (first < second)
first
else
second
}
}
這是一種解決方案,我們還有一種解決方案就是通過隱式引數的隱式轉換來實現,程式碼如下:
object ImplicitParameter extends App {
//下面程式碼中的(implicit order:T=>Ordered[T])
//給函式compare指定了一個隱式引數
//該隱式引數是一個隱式轉換
def compare[T](first:T,second:T)(implicit order:T=>Ordered[T])={
if (first > second)
first
else
second
}
println(compare("A","B"))
}
2. 函式中隱式引數使用概要
要點1:在定義函式時,如果函式沒有柯里化,implicit關鍵字會作用於所有引數,例如:
//implicit關鍵字在下面的函式中只能出現一次
//它作用於兩個引數x,y,也即x,y都是隱式引數
def sum(implicit x: Int, y: Int) = x + y
//下面的函式不合法,函式如果沒有柯里化,不能期望
//implicit關鍵字會作用於其中一個引數
//def sum(x: Int, implicit y: Int) = x + y
//def sum(implicit x: Int, implicit y: Int) = x + y
另外,值得注意的是,def maxFunc(implicit x: Int, y: Int) = x + y
在使用時,也只能指定一個隱式值,即指定的隱式值分別會對應函式中的引數(這裡是x,y),程式碼如下:
def sum(implicit x: Int, y: Int) = x + y
//只能指定一個隱式值
//例如下面下定義的x會自動對應maxFunc中的
//引數x,y即x=3,y=3,從而得到的結果是6
implicit val x:Int=3
//不能定義兩個隱式值
//implicit val y:Int=4
println(sum)
要點2:要想使用implicit只作用於某個函式引數,則需要將函式進行柯里化,如:
def sum(x: Int)(implicit y:Int)=x+y
值得注意的是,下面這種兩種帶隱式引數的函式也是不合法的
def sum(x: Int)(implicit y:Int)(d:Int)=x+y+d
def sum(x: Int)(implicit y:Int)(implicit d:Int)=x+y+d
要點3: 匿名函式不能使用隱式引數,例如:
val sum2=(implicit x:Int)=>x+1
要點4: 如何函式帶有隱式引數,則不能使用其偏函式,例如:
def sum(x: Int)(implicit y:Int)=x+y
//不能定義sum的偏函式,因為它帶有隱式引數
//could not find implicit value for
//parameter y: Int
//not enough arguments for method sum:
// (implicit y: Int)Int. Unspecified value parameter y.
def sum2=sum _
3. 隱式轉換問題梳理
1 多次隱式轉換問題
在上一講中我們提到,隱式轉換從源型別到目標型別不會多次進行,也即源型別到目標型別的轉換隻會進行一次
class RichFile(val file:File){
def read=Source.fromFile(file).getLines().mkString
}
//RichFileAnother類,裡面定義了read2方法
class RichFileAnother(val file:RichFile){
def read2=file.read
}
//隱式轉換不會多次進行,下面的語句會報錯
//不能期望會發生File到RichFile,然後RifchFile到
//RichFileAnthoer的轉換
val f=new File("file.log").read2
println(f)
注意這裡指的是源型別到目標型別的轉換隻會進行一次,並不是說不存在多次隱式轉換,在一般的方法呼叫過程中可能會出現多次隱式轉換,例如:
class ClassA {
override def toString() = "This is Class A"
}
class ClassB {
override def toString() = "This is Class B"
}
class ClassC {
override def toString() = "This is ClassC"
def printC(c: ClassC) = println(c)
}
class ClassD
object ImplicitWhole extends App {
implicit def B2C(b: ClassB) = {
println("B2C")
new ClassC
}
implicit def D2C(d: ClassD) = {
println("D2C")
new ClassC
}
//下面的程式碼會進行兩次隱式轉換
//因為ClassD中並沒有printC方法
//因為它會隱式轉換為ClassC(這是第一次,D2C)
//然後呼叫printC方法
//但是printC方法只接受ClassC型別的引數
//然而傳入的引數型別是ClassB
//型別不匹配,從而又發生了一次隱式轉地換(這是第二次,B2C)
//從而最終實現了方法的呼叫
new ClassD().printC(new ClassB)
}
還有一種情況也會發生多次隱式轉換,如果給函式定義了隱式引數,在實際執行過程中可能會發生多次隱式轉換,程式碼如下:
object Main extends App {
class PrintOps() {
def print(implicit i: Int) = println(i);
}
implicit def str2PrintOps(s: String) = {
println("str2PrintOps")
new PrintOps
}
implicit def str2int(implicit s: String): Int = {
println("str2int")
Integer.parseInt(s)
}
implicit def getString = {
println("getString")
"123"
}
//下面的程式碼會發生三次隱式轉換
//首先編譯器發現String型別是沒有print方法的
//嘗試隱式轉換,利用str2PrintOps方法將String
//轉換成PrintOps(第一次)
//然後呼叫print方法,但print方法接受整型的隱式引數
//此時編譯器會搜尋隱式值,但程式裡面沒有給定,此時
//編譯器會嘗試呼叫 str2int方法進行隱式轉換,但該方法
//又接受一個implicit String型別引數,編譯器又會嘗試
//查詢一個對應的隱式值,此時又沒有,因此編譯器會嘗試呼叫
//getString方法對應的字串(這是第二次隱式轉換,
//獲取一個字串,從無到有的過程)
//得到該字串後,再呼叫str2int方法將String型別字串
//轉換成Int型別(這是第三次隱式轉換)
"a".print
}
上面這個例子來源於:愛國者的部落格,感謝該作者的無私奉獻
2 要不要用隱式轉換的問題
從上述程式碼中可以看到,隱式轉換功能很強大,但同時也帶來了程式複雜性性問題,在一個程式中如果大量運用隱式轉換,特別是涉及到多次隱式轉換時,會使程式碼理解起來變得比較困難,那到底要不要用隱式轉換呢?下面給出我自己開發實踐中的部分總結,供大家參考:
1 即使你能輕鬆駕馭scala語言中的隱式轉換,能不用隱式轉換就儘量不用
2 如果一定要用,在涉及多次隱式轉換時,必須要說服自己這樣做的合理性
3 如果只是炫耀自己的scala語言能力,請大膽使用
新增公眾微訊號,可以瞭解更多最新Spark、Scala相關技術資訊