[关闭]
@runzhliu 2017-12-24T01:16:13.000000Z 字数 2662 阅读 685

Scala 隐式转换介绍

scala 隐式转换


本文为《快学 Scala》以及《深入了解 Scala》的读书笔记


1 概述

Scala 的隐式转换系统提供了一套良好的查找机制,可以让编译器能够调整代码,也就是说即使在写代码的时候故意漏掉一些信息,也可以让编译器尝试在编译期自动推导出来。Scala 编译器可以推导以下两种情况:

  1. 缺少参数的方法调用或构造器调用。
  2. 缺少了从一种类型到另一种类型的转换。

要理解隐式转换,首先要了解作用域的问题。然后是隐式解析的规则。


2 隐式参数和隐式转换

程序员应该谨慎明智地使用隐式对象,滥用隐式会让读者无法理解代码的。执行上下文是隐式的一个不错的使用场景,在编写事务、数据库连接、线程池以及用户会话时,也是不错的场景。这种使用方法可以让 API 更加简洁。

2.1 绕开类型擦除带来的限制

JVM 忘记了为参数化类型提供类型参数。编译器是不允许同时出现以下方法的定义,但是我们可以通过隐式参数来解决这个问题。

  1. // 在 REPL 上单独命名两个方法是可以的,但是用 :paste 模式就不行了
  2. object M {
  3. def m(seq: Seq[Int]): Unit = println(s"Seq[Int]: $seq")
  4. def m(seq: Seq[String]): Unit = println(s"Seq[String]: $seq")
  5. }
  6. // 解决方法
  7. // m 可以是两种不同的类型,编译都可以通过
  8. object M {
  9. implicit object IntMarker
  10. implicit object StringMarker
  11. def m(seq: Seq[Int])(implicit i: IntMarker.type): Unit = println(s"Seq[Int]: $seq")
  12. def m(seq: Seq[String])(implicit i:StringMarker.type): Unit = println(s"Seq[String]: $seq")
  13. }
  14. import M._
  15. m(List(1, 2, 3)
  16. m(List("one", "two", "three")
  1. 隐式参数,是一个单参数的函数
  2. 隐式转换,用于在类型之间做转换

隐式转换函数是指那种以 implicit 关键字声明的带有单个参数的函数。这样的函数被自动应用,将值从一种类型转换为另一种类型。

  1. // 如果是 Int 类型,则会为自动应用 int2Fraction 这个隐式转换函数
  2. implicit def int2Fraction(n: Int) = Fraction(n, 1)
  3. val result = 3 * Fraction(4, 5)

可以利用隐式转换丰富现有类库的功能

  1. # File 对象被隐式转换为 RichFile
  2. class RichFile(val from: File) {
  3. def read = Source.fromFile(from.getPath).mkString
  4. }
  5. implicit def file2RichFile(from: File) = new RichFile(from)

Scala 会考虑如下的隐式转换函数
1. 位于源或目标类型的伴生对象中的隐式函数
2. 位于当前作用域可以以单个标识符指代的隐式函数

  1. # 注意引入自定义的隐式转换函数的处理方法
  2. import com.horsemann.impatient.FractionConversions
  3. # 这个才可以将隐式方法本身传入
  4. import com.horsemann.impatient.FractionConversions._

由于以上方法引入隐式转换函数具有全句化,为了避免这种问题,应该采取局部化。

  1. # 避免某个特定的隐式转换带来麻烦
  2. import com.horsemann.impatient.FractionConversions.{fraction2Doubel => _, _}

如果想知道编译器用了哪些隐式转换,可以用如下命令行参数来编译程序:

  1. scalac -Xprint:typer MyProg.scala

隐式参数,函数或者方法可以带有一个标记为 implicit 的参数列表。这种情况下,编译器将会去找缺省值,提供给该函数或方法。

  1. case class Delimiters(left: String, right: String)
  2. def quote(what: String)(implicit delims: Delimiters) =
  3. delims.left + what + delims.right
  4. # 可以显式的 Delimiters 对象来调用 quote 方法
  5. quote("Bonjour le monde")

缺省情况下编译器会查找一个类型为 Delimiters 的隐式值,这必须是一个被声明为 implicit 的值

隐式的函数参数也可以被用作隐式转换。

  1. # 编译报错,因为并不知道 a 或者 b 是属于一个带有 < 操作符的类型
  2. def smaller[T](a: T, b:T) = if (a < b) a else b
  3. # 可以提供一个转换函数来达到目的
  4. def smaller[T](a: T, b: T)(implicit order: T => Ordered[T]) =
  5. if (order(a) < b) a else b

2.2 典型的隐式转换

定义 -> 方法的封装对象,Scala 已经在 Predef 对象中定义了该对象:

  1. implicit final class ArrowAssoc[A](val self: A) {
  2. def -> [B](y: B): Tuple2[A, B] = Tuple2(self, y)
  3. }

能够执行隐式转换的无外乎两类:构造方法只接受单一参数的类型或者只接受单一参数的方法。

2.3 Scala 内置的各种隐式

Scala 2.11 版库源代码中定义了超过300个隐式方法、隐式值和隐式类型。
Scala 的数值对象拥有大量的可以用于转换类型的伴生对象及隐式方法。

  1. scala> val b = 1
  2. b: Int = 1
  3. scala> Big
  4. BigDecimal BigInt
  5. scala> BigInt(b)
  6. res2: scala.math.BigInt = 1

Predef 中定义了大多数的隐式定义。其中的一些隐式定义包含了 @inline 标注。这一标注鼓励编译器努力尝试将函数调用内联 inline,以减少栈帧的开销。与之对应的是 @noinline 标注。
一些方法能将某一类型转化为另一类型,例如将某一类型封装成新的类型后,新的类型就会提供新的方法。

出于性能的考虑,Scala 会尽可能避免执行类型转换。不过因为目标集合的抽象体是建立在底层容器的基础上,因此并不会造成太多的性能消耗。


添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注