本文为《快学 Scala》以及《深入了解 Scala》的读书笔记
1 概述
Scala 的隐式转换系统提供了一套良好的查找机制,可以让编译器能够调整代码,也就是说即使在写代码的时候故意漏掉一些信息,也可以让编译器尝试在编译期自动推导出来。Scala 编译器可以推导以下两种情况:
-
缺少参数的方法调用或构造器调用。
-
缺少了从一种类型到另一种类型的转换。
要理解隐式转换,首先要了解作用域的问题。然后是隐式解析的规则。
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")
-
隐式参数,是一个单参数的函数
-
隐式转换,用于在类型之间做转换
隐式转换函数是指那种以 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 会考虑如下的隐式转换函数
-
位于源或目标类型的伴生对象中的隐式函数
-
位于当前作用域可以以单个标识符指代的隐式函数
# 注意引入自定义的隐式转换函数的处理方法
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 会尽可能避免执行类型转换。不过因为目标集合的抽象体是建立在底层容器的基础上,因此并不会造成太多的性能消耗。
网友评论