Scala程式設計學習之9-第 9 章隱式轉換、隱式函式,隱式值
9.1隱式轉換
9.1.1提出問題
先看一段程式碼,引出隱式轉換的實際需要=>指定某些資料型別的相互轉化
object Scala01 {
def main(args: Array[String]): Unit = {
val num : Int = 3.5 //?錯
val num2:Int = 4.6//? 錯 高精度->低精度 ==》1. 強制轉換 2. 隱式轉換
println(num)
}
}
9.1.2隱式函式基本介紹
隱式轉換函式是以implicit關鍵字宣告的帶有單個引數的函式。這種函式將會自動應用,將值從一種型別轉換為另一種型別
9.1.3隱式函式快速入門
使用隱式函式可以優雅的解決資料型別轉換,以前面的案例入門.
程式碼演示:
object ImplicitDemo { def main(args: Array[String]): Unit = { //使用隱式函式 implicit def f1(d:Double): Int = { //底層會生成一個方法 f1$1 d.toInt } val n1: Int = 3.4 //=> val n1: Int = f1$1(3.4) val n2: Int = 5.6 val n3: Int = 1.2 println(n1 + " " + n2 + " " + n3) } }
9.1.4隱式轉換的注意事項和細節
1)隱式轉換函式的函式名可以是任意的,隱式轉換與函式名稱無關,只與函式簽名(函式引數型別和返回值型別)有關。
2)隱式函式可以有多個(即:隱式函式列表),但是需要保證在當前環境下,只有一個隱式函式能被識別
9.2隱式轉換豐富類庫功能
9.2.1基本介紹
如果需要為一個類增加一個方法,可以通過隱式轉換來實現。(動態增加功能)比如想為MySQL類增加一個delete方法
9.2.2分析解決方案
在當前程式中,如果想要給MySQL類增加功能是非常簡單的,但是在實際專案中,如果想要增加新的功能就會需要改變原始碼,這是很難接受的。而且違背了軟體開發的OCP開發原則 (開閉原則 open close priceple)
在這種情況下,可以通過隱式轉換函式給類動態新增功能。
9.2.3快速入門案例
object ImplicitDemo02 {
def main(args: Array[String]): Unit = {
//隱式函式
implicit def addDelete(mySQL: MySQL): DB = {
new DB
}
//建立MySQL物件
val mySQL = new MySQL
mySQL.delete() //底層(編譯器做的轉換)是這樣的: addDelete$1(mySQL).delete()
}
}
//類-insert
class MySQL {
def insert(): Unit = {
println("insert")
}
}
//類 DB-delete
class DB {
def delete(): Unit = {
println("delete...")
}
}
9.3隱式值
9.3.1基本介紹
隱式值也叫隱式變數,將某個形參變數標記為implicit,所以編譯器會在方法省略隱式引數的情況下去搜索作用域內的隱式值作為預設引數
9.3.2應用案例
object ImplicitValDemo01 {
def main(args: Array[String]): Unit = {
//這裡的 str1就是隱式值 [=》跑龍套]
implicit val str1: String = "jack"
//說明
//1.implicit name: String 就是一個隱式引數
//2.當呼叫hello的時候,沒有傳入實參,則編譯器會自動的將隱式值關聯到name上
def hello(implicit name: String): Unit = {
println(name + " hello")
}
hello //用到隱式值 底層 hello$1(str1)
}
}
9.4隱式類
9.4.1基本介紹
在scala2.10後提供了隱式類,可以使用implicit宣告類,隱式類的非常強大,同樣可以擴充套件類的功能,比前面使用隱式轉換豐富類庫功能更加的方便,在集合中隱式類會發揮重要的作用。
9.4.2隱式類使用有如下幾個特點:
1)其所帶的構造引數有且只能有一個
2)隱式類必須被定義在“類”或“伴生物件”或“包物件”裡,即隱式類不能是 頂級的(top-level objects)
3)隱式類不能是case class(case class在後續介紹 樣例類)
4)作用域內不能有與之相同名稱的識別符號
9.4.3快速入門案例
object ImplicitClass {
def main(args: Array[String]): Unit = {
//一個隱式類, 可以返回一個隱式類的例項,然後就可以呼叫隱式類的方法
implicit class DB1(val m: MySQL1) {
def addSuffix(): String = { //方法
m + " scala"
}
def sayHi(): Unit = {
println("sayHi..")
}
def sayHello(): Unit = {
println("hello")
m.sayOk()
}
}
val mySQL = new MySQL1
mySQL.sayOk() //
// 1.底層 DB1$1(mySQL).addSuffix()
// 2. DB1$1(mySQL) 返回的是 :ImplicitClass$DB1$2 例項
// 3. 通過返回的 ImplicitClass$DB1$2例項.addSuffix()
println(mySQL.addSuffix()) //DB1$1(mySQL).addSuffix()
mySQL.sayHi()
mySQL.sayHello()
}
}
class MySQL1 { //普通類
def sayOk(): Unit = {
println("sayOk")
}
}
9.5隱式的轉換時機
1)當方法中的引數的型別與目標型別不一致時
2)當物件呼叫所在類中不存在的方法或成員時,編譯器會自動將物件進行隱式轉換(根據型別)
3)案例
object ImplicitChange {
implicit def f2(d: Double): Int = {
d.toInt
}
//作用域,保證隱式轉換可以找到..
implicit def f4(cat:Cat): Dog = {
new Dog(cat.name)
}
def main(args: Array[String]): Unit = {
f1(1.1)
var n2: Int = 1.5
val cat = new Cat
f3(cat)
}
def f1(n: Int): Unit = {
println("n=" + n)
}
def f3(dog:Dog): Unit = {
}
}
class Dog(dName:String) {
var name:String = dName
}
class Cat {
var name:String = "tom貓"
}
object T {
//作用域
implicit def f4(cat:Cat): Dog = {
new Dog(cat.name)
}
}
9.6隱式解析機制
9.6.1即編譯器是如何查詢到缺失資訊的,解析具有以下兩種規則:
1)首先會在當前程式碼作用域下查詢隱式實體(隱式方法、隱式類、隱式物件)。(一般是這種情況)
2)如果第一條規則查詢隱式實體失敗,會繼續在隱式引數的型別的作用域裡查詢。型別的作用域是指與該型別相關聯的全部伴生模組,一個隱式實體的型別T它的查詢範圍如下(第二種情況範圍廣且複雜在使用時,應當儘量避免出現):
a) 如果T被定義為T with A with B with C,那麼A,B,C都是T的部分,在T的隱式解析過程中,它們的伴生物件都會被搜尋。
b) 如果T是引數化型別,那麼型別引數和與型別引數相關聯的部分都算作T的部分,比如List[String]的隱式搜尋會搜尋List的伴生物件和String的伴生物件。
c) 如果T是一個單例型別p.T,即T是屬於某個p物件內,那麼這個p物件也會被搜尋。
d) 如果T是個型別注入S#T,那麼S和T都會被搜尋。
9.7隱式轉換的前提
9.7.1在進行隱式轉換時,需要遵守兩個基本的前提:
1)不能存在二義性
隱式操作不能巢狀使用