1. 隱式轉(zhuǎn)換簡介
在scala語言當(dāng)中律歼,隱式轉(zhuǎn)換是一項(xiàng)強(qiáng)大的程序語言功能民镜,它不僅能夠簡化程序設(shè)計(jì),也能夠使程序具有很強(qiáng)的靈活性险毁。
在scala語言中制圈,隱式轉(zhuǎn)換是無處不在的,只不過scala語言為我們隱藏了相應(yīng)的細(xì)節(jié)畔况,例如scala中的類繼承層次結(jié)構(gòu)中:
它們存在固有的隱式轉(zhuǎn)換鲸鹦,不需要人工進(jìn)行干預(yù),例如Float在必要情況下自動(dòng)轉(zhuǎn)換為Double類型
在前面 關(guān)于 Scala 界定跷跪、隱式轉(zhuǎn)換的一些知識(三)——視圖界定 中我們也提到馋嗜,視圖界定可以跨越類層次結(jié)構(gòu)進(jìn)行,它背后的實(shí)現(xiàn)原理就是隱式轉(zhuǎn)換吵瞻,例如 Int 類型會視圖界定中會自動(dòng)轉(zhuǎn)換成 RichInt, 而 RichInt 實(shí)現(xiàn)了 Comparable 接口嵌戈,當(dāng)然這里面的隱式轉(zhuǎn)換也是 scala 語言為我們設(shè)計(jì)好的
本節(jié)將對隱式轉(zhuǎn)換中的隱式轉(zhuǎn)換函數(shù)覆积、隱式轉(zhuǎn)換規(guī)則、隱式參數(shù)進(jìn)行介紹熟呛,使大家明白如何自己實(shí)現(xiàn)隱式轉(zhuǎn)換操作宽档。
2. 隱式轉(zhuǎn)換函數(shù)
我們在介紹 視圖界定的時(shí)候,提到 視圖界定 的實(shí)現(xiàn) 用到了隱式轉(zhuǎn)換庵朝,并用 Int 隱式轉(zhuǎn)換成 RichInt 來舉例吗冤。而下面我們將再舉一個(gè)例子。
下列賦值如果沒有隱式轉(zhuǎn)換的話會報(bào)錯(cuò):
val x:Int = 3.5
添加隱式轉(zhuǎn)換函數(shù)后可以實(shí)現(xiàn) Double 類型到 Int 類型的賦值:
implicit def double2Int(x:Double) = x.toInt
val x:Int = 3.5
隱式轉(zhuǎn)換功能十分強(qiáng)大九府,可以快速地?cái)U(kuò)展現(xiàn)有類庫的功能贡歧,例如下面的代碼:
package cn.zyb
import java.io.File
import scala.io.Source
class RichFile(val file: File) {
def read = Source.fromFile(file).getLines().mkString
}
object ImplicitFunction extends App {
implicit def double2Int(x: Double) = x.toInt
var x: Int = 3.5
//隱式函數(shù)將java.io.File隱式轉(zhuǎn)換為 RichFile 類
implicit def file2RichFile(file: File) = new RichFile(file)
val f = new File("file.log").read
println(f)
}
3. 隱式轉(zhuǎn)換規(guī)則
我們下面會介紹什么時(shí)候會發(fā)生隱式轉(zhuǎn)換浓冒,什么時(shí)候不會發(fā)生肠骆。
3.1 何時(shí)觸發(fā)隱式轉(zhuǎn)換
什么時(shí)候會發(fā)生隱式轉(zhuǎn)換呢加矛?主要有以下幾種情況:
3.1.1 當(dāng)方法中參數(shù)的類型與實(shí)際類型不一致時(shí)
def f(x: Int) = x + 1
//方法中輸入的參數(shù)類型與實(shí)際類型不一致,會自動(dòng)尋找 隱式轉(zhuǎn)換函數(shù) double2Int(x: Double)
//如果 double2Int(x: Double) 的參數(shù)儡羔,和 f(x: Int) 實(shí)際輸入?yún)?shù)類型一致
//且 double2Int 轉(zhuǎn)換后的返回類型 和 f(x: Int) 函數(shù)定義的參數(shù)類型一致宣羊,則滿足匹配
//double 類型會自動(dòng)調(diào)用 此隱式函數(shù),轉(zhuǎn)換為 Int 類型汰蜘,再進(jìn)行方法的執(zhí)行
f(3.14)
3.1.2 當(dāng)調(diào)用類中不存在的方法或成員時(shí)
例如上面 RichFile 的代碼:
//File類的對象并不存在 read 方法仇冯,此時(shí)便會發(fā)生隱式轉(zhuǎn)換
//將File類轉(zhuǎn)換成 RichFile
val f = new File("file.log").read
3.2 何時(shí)不會觸發(fā)隱式轉(zhuǎn)換
3.2.1 編譯器可以正常編譯通過時(shí)
//下面幾條語句,不需要自己定義隱式轉(zhuǎn)換編譯就可以通過
//因此它不會發(fā)生前面定義的隱式轉(zhuǎn)換
scala> 3.0*2
res0: Double = 6.0
scala> 2*3.0
res1: Double = 6.0
scala> 2*3.7
res2: Double = 7.4
3.2.2 轉(zhuǎn)換存在二義性
//這里定義了一個(gè)隱式轉(zhuǎn)換
implicit def file2RichFile(file:File)=new RichFile(file)
//這里又定義了一個(gè)隱式轉(zhuǎn)換族操,目的與前面那個(gè)相同
implicit def file2RichFile2(file:File)=new RichFile(file)
//下面這條語句在編譯時(shí)會出錯(cuò)苛坚,報(bào)錯(cuò)信息如下(去除了無用信息)
//both method file2RichFile and method file2RichFile2
//are possible conversion functions from java.io.File to ?{def read: ?}
val f=new File("file.log").read
3.2.3 隱式轉(zhuǎn)換不會嵌套進(jìn)行
源類型到目標(biāo)類型的轉(zhuǎn)換只會進(jìn)行一次
下面看一下不完全代碼:
implicit def richFile2RichFileAnother(richFile: RichFile) = new RichFileAnother(richFile)
//RichFileAnother類,里面定義了read2方法
class RichFileAnother(val richFile: RichFile) {
def read2 = file.read
}
//隱式轉(zhuǎn)換不會多次進(jìn)行色难,下面的語句會報(bào)錯(cuò)
//不能期望會發(fā)生 File 到 RichFile泼舱,然后 RifchFile 到 RichFileAnthoer 的轉(zhuǎn)換
val f = new File("file.log").read2
4. 隱式參數(shù)
我們在 關(guān)于 Scala 界定、隱式轉(zhuǎn)換的一些知識(五)——上下文界定 中枷莉,說到 上下文界定用到了 隱式參數(shù)柠掂。如果給函數(shù)定義隱式參數(shù)的話,則在使用時(shí)可以不帶參數(shù)
class Pair[T: Ordering](val first: T, val second: T) {
//smaller 方法中有一個(gè)隱式參數(shù)依沮,該隱式參數(shù)類型為 Ordering[T]
def smaller(implicit ord: Ordering[T]) = {
if(ord.compare(first, second) > 0) first else second
}
}
implicit val p1 = new PersonOrdering
//不給函數(shù)指定參數(shù),此時(shí)會查找一個(gè)隱式值枪狂,該隱式值類型為 Ordering[Person]
//根據(jù)上下文界定的要求危喉,p1 正好滿足要求
//因此它會作為 smaller 的隱式參數(shù)傳入,從而調(diào)用 ord.compare(first,second) 方法進(jìn)行比較
val p = new Pair(Person("123"), Person("456"))
//調(diào)用時(shí)并沒有傳入隱式參數(shù)
p.smaller
5. 隱式參數(shù)中的隱式轉(zhuǎn)換
前一講中州疾,我們提到函數(shù)中如果存在隱式參數(shù)辜限,在使用該函數(shù)時(shí) 編譯器會自動(dòng)幫我們搜索相應(yīng)的隱式值,并將該隱式值作為函數(shù)的參數(shù)
這里面其實(shí)沒有涉及到隱式轉(zhuǎn)換严蓖,本節(jié)將演示如何利用隱式參數(shù)進(jìn)行隱式轉(zhuǎn)換薄嫡,下面的代碼給定的是一個(gè)普通的比較函數(shù):
object ImplicitParameter extends App {
//下面的代碼不能編譯通過氧急,這里面泛型T沒有具體指定
//它不能直接使用 < 符號進(jìn)行比較
def compare[T](first: T, second: T) = {
if (first < second) first else second
}
}
面的代碼要想使其編譯通過,可以為 T 指定其上界為 Ordered[T]
object ImplicitParameter extends App {
def compare[T <: Ordered[T]](first: T, second: T) = {
if (first < second) first else second
}
}
這是一種解決方案毫深,我們還有一種解決方案就是通過隱式參數(shù)的隱式轉(zhuǎn)換來實(shí)現(xiàn)吩坝,代碼如下:
object ImplicitParameter extends App {
def compare[T](first: T, second: T)(implicit order: T => Ordered[T]) = {
if (first < second) first else second
}
}
6. 隱式轉(zhuǎn)換問題梳理
6.1 多次隱式轉(zhuǎn)換問題
在 3.2.3 隱式轉(zhuǎn)換不會嵌套進(jìn)行 中我們提到,源類型到目標(biāo)類型的轉(zhuǎn)換只會進(jìn)行一次哑蔫,并不是說不存在多次隱式轉(zhuǎn)換钉寝,在一般的方法調(diào)用過程中可能會出現(xiàn)多次隱式轉(zhuǎn)換,例如:
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
}
//下面的代碼會進(jìn)行兩次隱式轉(zhuǎn)換
//因?yàn)镃lassD中并沒有printC方法
//因?yàn)樗鼤[式轉(zhuǎn)換為ClassC(這是第一次,D2C)
//然后調(diào)用printC方法
//但是printC方法只接受ClassC類型的參數(shù)
//然而傳入的參數(shù)類型是ClassB
//類型不匹配闸迷,從而又發(fā)生了一次隱式轉(zhuǎn)地?fù)Q(這是第二次,B2C)
//從而最終實(shí)現(xiàn)了方法的調(diào)用
new ClassD().printC(new ClassB)
}
還有一種情況也會發(fā)生多次隱式轉(zhuǎn)換嵌纲,如果給函數(shù)定義了隱式參數(shù),在實(shí)際執(zhí)行過程中可能會發(fā)生多次隱式轉(zhuǎn)換腥沽,代碼如下:
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"
}
//下面的代碼會發(fā)生三次隱式轉(zhuǎn)換
//首先編譯器發(fā)現(xiàn)String類型是沒有print方法的
//嘗試隱式轉(zhuǎn)換逮走,利用str2PrintOps方法將String
//轉(zhuǎn)換成PrintOps(第一次)
//然后調(diào)用print方法,但print方法接受整型的隱式參數(shù)
//此時(shí)編譯器會搜索隱式值今阳,但程序里面沒有給定师溅,此時(shí)
//編譯器會嘗試調(diào)用 str2int方法進(jìn)行隱式轉(zhuǎn)換,但該方法
//又接受一個(gè)implicit String類型參數(shù)酣栈,編譯器又會嘗試
//查找一個(gè)對應(yīng)的隱式值险胰,此時(shí)又沒有,因此編譯器會嘗試調(diào)用
//getString方法對應(yīng)的字符串(這是第二次隱式轉(zhuǎn)換矿筝,
//獲取一個(gè)字符串起便,從無到有的過程)
//得到該字符串后,再調(diào)用str2int方法將String類型字符串
//轉(zhuǎn)換成Int類型(這是第三次隱式轉(zhuǎn)換)
"a".print
}
6.2 要不要用隱式轉(zhuǎn)換的問題
從上述代碼中可以看到窖维,隱式轉(zhuǎn)換功能很強(qiáng)大榆综,但同時(shí)也帶來了程序復(fù)雜性性問題,在一個(gè)程序中如果大量運(yùn)用隱式轉(zhuǎn)換铸史,特別是涉及到多次隱式轉(zhuǎn)換時(shí)鼻疮,會使代碼理解起來變得比較困難,那到底要不要用隱式轉(zhuǎn)換呢琳轿?下面給出我自己開發(fā)實(shí)踐中的部分總結(jié)判沟,供大家參考:
- 即使你能輕松駕馭 scala 語言中的隱式轉(zhuǎn)換,能不用隱式轉(zhuǎn)換就盡量不用
- 如果一定要用崭篡,在涉及多次隱式轉(zhuǎn)換時(shí)挪哄,必須要說服自己這樣做的合理性
- 如果只是炫耀自己的scala語言能力,請大膽使用