天天看點

從JAVA到Scala(三):implicit的三種用法

對scala的隐式參數又愛又恨,既強大得讓人歡呼雀躍,但繁瑣的機制與晦澀的文法又讓人望而卻步,總結起來,implicit一共有三種用法,一種比一種強大,這裡就不講太深奧的理論,直接進入應用場景實戰。

1. 預設參數值

首先,需要注意的是,在方法參數上添加implicit關鍵字對方法幾乎沒有任何影響,原來的調用方式依舊。

其次,implicit作為參數聲明時,隻能作為方法的唯一參數,是的,你沒看錯,唯一的參數,還有每個方法也隻能聲明一個參數!如果混在一起,那麼它幾乎沒有作用,因為每一次調用時你都必須指定方法的所有參數。當然,scala提供了完美的解決辦法,請看下面的例子:

test("隐式參數必須獨立聲明") {
    //  調用時無法省略參數
    def add(implicit a: Int, b: Int): Int = a + b
    //  一定要作為最後一個參數
    def plus(a: Int)(implicit b: Int): Int = a + b
    //  聲明隐式參數,不需要名稱相同,隻能類型相同就可以了
    implicit val c: Int = 
    //  單元測試
    assertResult(plus())()
}
           

從上面的例子可以得出如下結論:

1. 通過高階函數的方法,隐式參數可以和其他參數一起合作,但受限于調用方式,必須作為函數的最後一個參數;

2. 聲明隐式參數時,并不需要采用與函數參數一樣的名稱,隻要類型相同即可;

2. 參數類型轉換

在方法的調用中,實參的類型有時難以比對形參的類型,此時需要可以利用隐式方法進行自動轉換

test("隐式方法轉換類型") {
    //  調用的方法,并不需要聲明為隐式參數
    def plus(a: Int, b: Int): Int = a + b
    //  定義轉換函數
    implicit def stringToInt(s: String): Int = Integer.parseInt(s)
    assertResult(plus("3", "5"))()
  }
           

從上面的例子中,我們可以看出:

1. 調用的方法不需要攜帶隐式參數,适用于所有的方法;

2. 定義轉換函數後,大大增強了方法參數的适配性;

3. 隐式函數查找的作用域跟隐式參數的作用域一模一樣;

3. 動态添加屬性與方法

除了可以批量指派預設參數,以及動态轉換函數外,implicit最強大的地方還在于能為每種類型動态添加屬性與方法,如下:

test("動态添加屬性與方法") {
    //  參數就是添加屬性與方法的類型
   implicit def strFile(dir: String) = new {
      //  動态添加屬性
      val isDir:Boolean = true
      //  動态添加方法
      def listDir(): List[File] = {
        val d = new File(dir)
        //  _隻有作為參數時才能省略,作為主體時不能省略
        d.listFiles().filter(_.isDirectory).toList
      }
    }
    assert("/".listDir().size > )
    assert("/".isDir)
}
           

這個功能簡直強大到吓人,任意類型一律通殺,無視類型的聲明是否是不變模型(final),無視類型是自定義還是系統内置。有了這樣的機制,寫出一萬公裡長的鍊式代碼也是易如反掌。

4. 隐式作用域

為了找到相關的隐式參數與方法,scala會按如下優先級查找相關的隐式聲明:

1. 在目前函數調用的作用域及父作用域内;

2. 在聲明隐式參數類型的伴生作用域内;

結論

簡簡單單的implicit關鍵字,對Scala的功能增強幾乎是天翻地覆,隻有了解了implicit,尤其是第三種用法,才能了解如同天書一樣的DSL代碼(尤其是初次使用的JAVA程式員),強烈推薦《深入了解Scala》(這本書翻譯得不太好,但内容很有深度)進一步閱讀。

上一篇: Beats 基礎3