Scala 隱式轉換介紹

本文為《快學 Scala》以及《深入了解 Scala》的讀書筆記


1 概述

Scala 的隱式轉換系統提供了一套良好的查找機制,可以讓編譯器能夠調整代碼,也就是說即使在寫代碼的時候故意漏掉一些信息,也可以讓編譯器嘗試在編譯期自動推導出來。Scala 編譯器可以推導以下兩種情況:

  1. 缺少參數的方法調用或構造器調用。

  2. 缺少了從一種類型到另一種類型的轉換。

要理解隱式轉換,首先要了解作用域的問題。然后是隱式解析的規則。


2 隱式參數和隱式轉換

程序員應該謹慎明智地使用隱式對象,濫用隱式會讓讀者無法理解代碼的。執行上下文是隱式的一個不錯的使用場景,在編寫事務、數據庫連接、線程池以及用戶會話時,也是不錯的場景。這種使用方法可以讓 API 更加簡潔。

2.1 繞開類型擦除帶來的限制

JVM 忘記了為參數化類型提供類型參數。編譯器是不允許同時出現以下方法的定義,但是我們可以通過隱式參數來解決這個問題。


// 在 REPL 上單獨命名兩個方法是可以的,但是用 :paste 模式就不行了

object M {

def m(seq: Seq[Int]): Unit = println(s"Seq[Int]: $seq")

def m(seq: Seq[String]): Unit = println(s"Seq[String]: $seq")

}

// 解決方法

// m 可以是兩種不同的類型,編譯都可以通過

object M {

implicit object IntMarker

implicit object StringMarker

def m(seq: Seq[Int])(implicit i: IntMarker.type): Unit = println(s"Seq[Int]: $seq")

def m(seq: Seq[String])(implicit i:StringMarker.type): Unit = println(s"Seq[String]: $seq")

}

import M._

m(List(1, 2, 3)

m(List("one", "two", "three")

  1. 隱式參數,是一個單參數的函數

  2. 隱式轉換,用于在類型之間做轉換

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


// 如果是 Int 類型,則會為自動應用 int2Fraction 這個隱式轉換函數

implicit def int2Fraction(n: Int) = Fraction(n, 1)

val result = 3 * Fraction(4, 5)

可以利用隱式轉換豐富現有類庫的功能


# File 對象被隱式轉換為 RichFile

class RichFile(val from: File) {

def read = Source.fromFile(from.getPath).mkString

}

implicit def file2RichFile(from: File) = new RichFile(from)

Scala 會考慮如下的隱式轉換函數

  1. 位于源或目標類型的伴生對象中的隱式函數

  2. 位于當前作用域可以以單個標識符指代的隱式函數


# 注意引入自定義的隱式轉換函數的處理方法

import com.horsemann.impatient.FractionConversions

# 這個才可以將隱式方法本身傳入

import com.horsemann.impatient.FractionConversions._

由于以上方法引入隱式轉換函數具有全句化,為了避免這種問題,應該采取局部化。


# 避免某個特定的隱式轉換帶來麻煩

import com.horsemann.impatient.FractionConversions.{fraction2Doubel => _, _}

如果想知道編譯器用了哪些隱式轉換,可以用如下命令行參數來編譯程序:


scalac -Xprint:typer MyProg.scala

隱式參數,函數或者方法可以帶有一個標記為 implicit 的參數列表。這種情況下,編譯器將會去找缺省值,提供給該函數或方法。


case class Delimiters(left: String, right: String)

def quote(what: String)(implicit delims: Delimiters) =

delims.left + what + delims.right

# 可以顯式的 Delimiters 對象來調用 quote 方法

quote("Bonjour le monde")

缺省情況下編譯器會查找一個類型為 Delimiters 的隱式值,這必須是一個被聲明為 implicit 的值

隱式的函數參數也可以被用作隱式轉換。


# 編譯報錯,因為并不知道 a 或者 b 是屬于一個帶有 < 操作符的類型

def smaller[T](a: T, b:T) = if (a < b) a else b

# 可以提供一個轉換函數來達到目的

def smaller[T](a: T, b: T)(implicit order: T => Ordered[T]) =

if (order(a) < b) a else b

2.2 典型的隱式轉換

定義 -> 方法的封裝對象,Scala 已經在 Predef 對象中定義了該對象:


implicit final class ArrowAssoc[A](val self: A) {

def -> [B](y: B): Tuple2[A, B] = Tuple2(self, y)

}

能夠執行隱式轉換的無外乎兩類:構造方法只接受單一參數的類型或者只接受單一參數的方法。

2.3 Scala 內置的各種隱式

Scala 2.11 版庫源代碼中定義了超過300個隱式方法、隱式值和隱式類型。

Scala 的數值對象擁有大量的可以用于轉換類型的伴生對象及隱式方法。


scala> val b = 1

b: Int = 1

scala> Big

BigDecimal  BigInt

scala> BigInt(b)

res2: scala.math.BigInt = 1

Predef 中定義了大多數的隱式定義。其中的一些隱式定義包含了 @inline 標注。這一標注鼓勵編譯器努力嘗試將函數調用內聯 inline,以減少棧幀的開銷。與之對應的是 @noinline 標注。

一些方法能將某一類型轉化為另一類型,例如將某一類型封裝成新的類型后,新的類型就會提供新的方法。

出于性能的考慮,Scala 會盡可能避免執行類型轉換。不過因為目標集合的抽象體是建立在底層容器的基礎上,因此并不會造成太多的性能消耗。


?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容