九、Scala從入門到精通一一隱式轉換和隱式值

9.1、隱式轉換

9.1.1、提出問題

先看一段代碼,引出隱式轉換的實際需要=>指定某些數據類型的相互轉化

package com.atguigu.scala.conversion

object Scala01 {
  def main(args: Array[String]): Unit = {
    val num : Int = 3.5 //?錯 高精度->低精度
    println(num)
  }
}

9.1.2、隱式函數基本介紹

隱式轉換函數是以implicit關鍵字聲明的帶有單個參數的函數。這種函數將會自動應用,將值從一種類型轉換爲另一種類型

9.1.3、隱式函數快速入門

使用隱式函數可以優雅的解決數據類型轉換,以前面的案例入門
代碼演示

object ImplicitDemo01 {
  def main(args: Array[String]): Unit = {
    // 編寫一個隱式函數轉成 Double => Int 轉換
    // 隱式函數應當在作用域才能生效
    implicit def f1(d: Double): Int = { //底層 生成 f1$1
      d.toInt
    }

    implicit def f2(f: Float): Int = {
      f.toInt
    }
    // 這裏我們必須保證隱式函數的匹配只能是唯一的
    implicit def f3(f: Float): Int = {
      f.toInt
    }

    val num: Int = 3.5 // 底層辨析 f1$1(3.5)
    //val num2: Int = 4.5f // error
    println("num = " + num)
  }
}

反編譯後的代碼:
在這裏插入圖片描述

9.1.4、隱式轉換的注意事項和細節

1、隱式轉換函數的函數名可以是任意的,隱式轉換與函數名稱無關,只與函數簽名(函數參數類型和返回值類型)有關。
2、隱式函數可以有多個(即:隱式函數列表),但是需要保證在當前環境下,只有一個隱式函數能被識別

9.2、隱式轉換豐富類庫功能

9.2.1、快速入門案例

使用隱式轉換方式動態的給MySQL類增加delete方法

object ImplicitDemo02 {
  def main(args: Array[String]): Unit = {

    // 編寫一個隱式函數,豐富mysql功能
    implicit def addDelect(msql: MySQL): DB = {
      new DB
    }

    // 創建mysql對象
    val mysql = new MySQL
    mysql.update()
    mysql.insert()
    mysql.delete() // 分析:
                        // 1.在主函數外面 addDelect(mysql).delete()
                       // 2.在主函數裏面 addDelect$1(mysql).delete()
  }
}

class MySQL {
  def insert(): Unit = {
    println("Insert")
  }
}

class DB {
  def delete(): Unit = {
    println("Delete")
  }
  def update(): Unit ={
    println("Update")
  }
}

9.3、隱式值

9.3.1、基本介紹

隱式值也叫隱式變量,將某個形參變量標記爲implicit,所以編譯器會在方法省略隱式參數的情況下去搜索作用域內的隱式值作爲缺省參數

9.3.2、快速入門

object ImplicitDemo03 {
  def main(args: Array[String]): Unit = {
    implicit val str1: String = "jack" // 這個就是隱式值

    // implicit name: String  name就是隱式參數
    def hello(implicit name: String ): Unit = {
      println(name + " Hello")
    }

    hello // 底層 hello$1(str1);
  }
}

9.3.3、一個案例說明隱式值,默認值,傳值的優先級

// 小結
// 1.當在程序中,同時有隱式值,默認值,傳值
// 2.編譯器的優先級爲 傳值> 隱式值 > 默認值
// 3.在隱式值匹配時,不能有二義性
// 4.如果三個 (隱式值,默認值,傳值) 一個都沒有,就會報錯
object ImplicitVal02 {
  def main(args: Array[String]): Unit = {
    // 隱式變量 (值)
    //implicit val name:String = "Scala"
    //implicit val name1:String = "World"

    // 隱式參數
    def Hello(implicit content: String = "Jack"): Unit = {
      println("Hello1 " + content)
    } // 調用Hello
    Hello

    // 當同時有implicit 值和默認值,implicit 優先級高
    def Hello2(implicit content: String = "Jack"): Unit = {
      println("Hello2 " + content)
    } // 調用Hello
    Hello2

    // 說明
    // 1.當一個隱式參數匹配不到隱式值,仍然會使用默認值
    implicit val name: Int = 10

    def Hello3(implicit content: String = "Jack"): Unit = {
      println("Hello3 " + content)
    } // 調用Hello
    Hello3

    // 當沒有默認值,又沒有傳值,就會報錯

    def Hello4(implicit content: String): Unit = {
      println("Hello4 " + content)
    } // 調用Hello
    Hello4("zx") // error
  }
}

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 ImplicitClassDemo {
  def main(args: Array[String]): Unit = {
    // DB1會生成隱式類
    // DB1是一個隱式類,當我們在該隱式類的作用域範圍,創建MySQL1實例
    // 該隱式類就會生效. 這個轉換的工作仍然是編譯器完成的
    // 看底層..
    implicit class DB1(val m: MySQL1) {
      def addSuffix(): String = {
        m + "Scala"
      }
    }

    // 創建MySQL1的實例
    val mySQL = new MySQL1
    mySQL.sayOk()
    mySQL.addSuffix() // 研究如何關聯到DB1   DB1$1(MySQL).addSuffix();

    implicit def f1(d:Double):Int = {
      d.toInt
    }

    def test1(n1:Int): Unit ={
      println("ok")
    }
    test1(10.1)
  }
}

class MySQL1 {
  def sayOk(): Unit = {
    println("sayOk")
  }
}

9.5、隱式的轉換時機

1、當方法中的參數的類型與目標類型不一致時

implicit def f1(d:Double):Int={
	d.toInt
}
def test1(n1:Int):Unit={
	println("ok")
}
	test1(10.1)

2、當對象調用所在類中不存在的方法或成員時,編譯器會自動將對象進行隱式轉換(根據類型)

9.6、隱式解析機制

即編譯器是如何查找到缺失信息的,解析具有以下兩種規則:
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、在進行隱式轉換時,需要遵守兩個基本的前提

1、不能存在二義性
2、隱式操作不能嵌套使用 // [舉例:]如:隱式轉換函數

object ImplicitNotice {
  def main(args: Array[String]): Unit = {

    // 1.隱式轉換不能有二義性
    // 2.隱式轉換不能嵌套使用

    implicit def f1(d:Double): Int ={

      d.toInt
     // val num2 :Int = 2.3 // 底層f1$1(2.3)  //f1$1對應的就是f1 就會形成遞歸調用
    }

    val num1 :Int = 1.1
  }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章