美文网首页
Scala 隐式转换介绍

Scala 隐式转换介绍

作者: runzhliu | 来源:发表于2018-06-03 08:21 被阅读0次

本文为《快学 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 会尽可能避免执行类型转换。不过因为目标集合的抽象体是建立在底层容器的基础上,因此并不会造成太多的性能消耗。


相关文章

网友评论

      本文标题:Scala 隐式转换介绍

      本文链接:https://www.haomeiwen.com/subject/ytodsftx.html