scala語言入門(四)隱式轉換
深入理解Scala的隱式轉換
摘要:
通過隱式轉換,程式設計師可以在編寫Scala程式時故意漏掉一些資訊,讓編譯器去嘗試在編譯期間自動推匯出這些資訊來,這種特性可以極大的減少程式碼量,忽略那些冗長,過於細節的程式碼。
使用方式:
1.將方法或變數標記為implicit
2.將方法的引數列表標記為implicit
3.將類標記為implicit
Scala支援兩種形式的隱式轉換:
隱式值:用於給方法提供引數
隱式檢視:用於型別間轉換或使針對某型別的方法能呼叫成功
隱式值:
例1:宣告person方法。其引數為name,型別String
scala> def person(implicit name : String) = name //name為隱式引數
person: (implicit name: String)String
直接呼叫person方法
scala> person
<console>:9: error: could not find implicit value for parameter name: String
person
^
報錯!編譯器說無法為引數name找到一個隱式值
定義一個隱式值後再呼叫person方法
scala> implicit val p = "mobin" //p被稱為隱式值
p: String = mobin
scala> person
res1: String = mobin
因為將p變數標記為implicit,所以編譯器會在方法省略隱式引數的情況下去搜索作用域內的隱式值作為缺少引數。
但是如果此時你又在REPL中定義一個隱式變數,再次呼叫方法時就會報錯
scala> implicit val p1 = "mobin1" p1: String = mobin1 scala> person <console>:11: error: ambiguous implicit values: both value p of type => String and value p1 of type => String match expected type String person ^
匹配失敗,所以隱式轉換必須滿足無歧義規則,在宣告隱式引數的型別是最好使用特別的或自定義的資料型別,不要使用Int,String這些常用型別,避免碰巧匹配
隱式檢視
隱式轉換為目標型別:把一種型別自動轉換到另一種型別
例2:將整數轉換成字串型別:
scala> def foo(msg : String) = println(msg)
foo: (msg: String)Unit
scala> foo(10)
<console>:11: error: type mismatch;
found : Int(10)
required: String
foo(10)
^
顯然不能轉換成功,解決辦法就是定義一個轉換函式給編譯器將int自動轉換成String
scala> implicit def intToString(x : Int) = x.toString
intToString: (x: Int)String
scala> foo(10)
10
隱式轉換呼叫類中本不存在的方法
例3:通過隱式轉換,使物件能呼叫類中本不存在的方法
class SwingType{
def wantLearned(sw : String) = println("兔子已經學會了"+sw)
}
object swimming{
implicit def learningType(s : AminalType) = new SwingType
}
class AminalType
object AminalType extends App{
import com.mobin.scala.Scalaimplicit.swimming._
val rabbit = new AminalType
rabbit.wantLearned("breaststroke") //蛙泳
}
編譯器在rabbit物件呼叫時發現物件上並沒有wantLearning方法,此時編譯器就會在作用域範圍內查詢能使其編譯通過的隱式檢視,找到learningType方法後,編譯器通過隱式轉換將物件轉換成具有這個方法的物件,之後呼叫wantLearning方法
可以將隱式轉換函式定義在伴生物件中,在使用時匯入隱式檢視到作用域中即可(如例4的learningType函式)
還可以將隱式轉換函式定義在凶物件中,同樣在使用時匯入作用域即可,如例4
例4:
class SwingType{
def wantLearned(sw : String) = println("兔子已經學會了"+sw)
}
package swimmingPage{
object swimming{
implicit def learningType(s : AminalType) = new SwingType //將轉換函式定義在包中
}
}
class AminalType
object AminalType extends App{
import com.mobin.scala.Scalaimplicit.swimmingPage.swimming._ //使用時顯示的匯入
val rabbit = new AminalType
rabbit.wantLearned("breaststroke") //蛙泳
}
像intToString,learningType這類的方法就是隱式檢視,通常為Int => String的檢視,定義的格式如下:
implicit def originalToTarget (<argument> : OriginalType) : TargetType
其通常用在於以兩種場合中:
1.如果表示式不符合編譯器要求的型別,編譯器就會在作用域範圍內查詢能夠使之符合要求的隱式檢視。如例2,當要傳一個整數型別給要求是字串型別引數的方法時,在作用域裡就必須存在Int => String的隱式檢視
2.給定一個選擇e.t,如果e的型別裡並沒有成員t,則編譯器會查詢能應用到e型別並且返回型別包含成員t的隱式檢視。如例3
隱式類:
在scala2.10後提供了隱式類,可以使用implicit宣告類,但是需要注意以下幾點:
1.其所帶的構造引數有且只能有一個
2.隱式類必須被定義在類,伴生物件和包物件裡
3.隱式類不能是case class(case class在定義會自動生成伴生物件與2矛盾)
4.作用域內不能有與之相同名稱的標示符
例5:
object Stringutils {
implicit class StringImprovement(val s : String){ //隱式類
def increment = s.map(x => (x +1).toChar)
}
}
object Main extends App{
import com.mobin.scala.implicitPackage.Stringutils._
println("mobin".increment)
}
編譯器在mobin物件呼叫increment時發現物件上並沒有increment方法,此時編譯器就會在作用域範圍內搜尋隱式實體,發現有符合的隱式類可以用來轉換成帶有increment方法的StringImprovement類,最終呼叫increment方法。
隱式轉換的時機:
1.當方法中的引數的型別與目標型別不一致時
2.當物件呼叫類中不存在的方法或成員時,編譯器會自動將物件進行隱式轉換
隱式解析機制
即編譯器是如何查詢到缺失資訊的,解析具有以下兩種規則:
1.首先會在當前程式碼作用域下查詢隱式實體(隱式方法 隱式類 隱式物件)
2.如果第一條規則查詢隱式實體失敗,會繼續在隱式引數的型別的作用域裡查詢
型別的作用域是指與該型別相關聯的全部伴生模組,一個隱式實體的型別T它的查詢範圍如下:
(1)如果T被定義為T with A with B with C,那麼A,B,C都是T的部分,在T的隱式解析過程中,它們的伴生物件都會被搜尋
(2)如果T是引數化型別,那麼型別引數和與型別引數相關聯的部分都算作T的部分,比如List[String]的隱式搜尋會搜尋List的
伴生物件和String的伴生物件
(3) 如果T是一個單例型別p.T,即T是屬於某個p物件內,那麼這個p物件也會被搜尋
(4) 如果T是個型別注入S#T,那麼S和T都會被搜尋
隱式轉換的前提:
1.不存在二義性(如例1)
2.隱式操作不能巢狀使用(如 convert1(covert2(x)))+y
3.程式碼能夠在不使用隱式轉換的前提下能編譯通過,就不會進行隱式轉換