什么是隐式转换

我们经常引入第三方库,但当我们想要扩展新功能的时候通常是很不方便的,因为我们不能直接修改其代码。scala提供了隐式转换机制和隐式参数帮我们解决诸如这样的问题。
Scala中的隐式转换是一种非常强大的代码查找机制。当函数、构造器调用缺少参数或者某一实例调用了其他类型的方法导致编译不通过时,编译器会尝试搜索一些特定的区域,尝试使编译通过。

场景一,现在我们要为Java的File类提供一个获得所有行数的方法:

  implicit class Files(file: File) {
def lines: Array[String] = {
val fileReader: FileReader = new FileReader(file)
val reader = new BufferedReader(fileReader)
try {
var lines = Array[String]()
var line = reader.readLine() while (line != null) {
lines = lines :+ line
line = reader.readLine()
}
lines
} finally {
fileReader.close()
reader.close()
}
}
} private val file: File = new File("/path/to") file.lines foreach println

场景二,我期望可以像操作集合那样来操作一个文件中的所有行。比如,对所有的行映射(map)一个指定函数。

  implicit def file2Array(file: File): Array[String] = file.lines

  def map[R](source: Array[String])(fn: String ⇒ R) = {
source.map(fn)
} map(new File("/path/to"))(println)

隐式操作规则

  1. 标记规则:只有标记为implicit的变量,函数或对象定义才能被编译器当做隐式操作目标。

  2. 作用域规则:插入的隐式转换必须是单一标示符的形式处于作用域中,或与源/目标类型关联在一起。单一标示符是说当隐式转换作用时应该是这样的形式:file2Array(arg).map(fn)的形式,而不是foo.file2Array(arg).map的形式。假设file2Array函数定义在foo对象中,我们应该通过import foo._或者import foo.file2Array把隐式转换导入。简单来说,隐式代码应该可以被"直接"使用,不能再依赖类路径。
    假如我们把隐式转换定义在源类型或者目标类型的伴生对象内,则我们可以跳过单一标示符的规则。因为编译器在编译期间会自动搜索源类型和目标类型的伴生对象,以尝试找到合适的隐式转换。

  3. 无歧义规则:不能存在多于一个隐式转换使某段代码编译通过。因为这种情况下会产生迷惑,编译器不能确定到底使用哪个隐式转换。

  4. 单一调用规则:不会叠加(重复嵌套)使用隐式转换。一次隐式转化调用成功之后,编译器不会再去寻找其他的隐式转换。

  5. 显示操作优先规则:当前代码类型检查没有问题,编译器不会尝试查找隐式转换。

隐式解析的搜索范围

隐式转换本身是一种代码查找机制,所以下面会介绍隐式转换的查找范围:
-当前代码作用域。最直接的就是隐式定义和当前代码处在同一作用域中。
-当第一种解析方式没有找到合适的隐式转换时,编译器会继续在隐式参数类型的隐式作用域里查找。一个类型的隐式作用域指的是与该类型相关联的所有的伴生对象。

对于一个类型T它的隐式搜索区域包括如下:
-假如T是这样定义的:T with A with B with C,那么A, B, C的伴生对象都是T的搜索区域。
-如果T是类型参数,那么参数类型和基础类型都是T的搜索部分。比如对于类型List[Foo],List和Foo都是搜索区域
-如果T是一个单例类型p.T,那么p和T都是搜索区域。
-如果T是类型注入p#T,那么p和T都是搜索区域。

所以,只要在上述的任何一个区域中搜索到合适的隐式转换,编译器都可以使编译通过。

看两个例子:

  1. 通过类型参数获得隐式作用域
scala> implicit val i: Int = 1
i: Int = 1
scala> implicitly[Int]
res11: Int = 1
  1. 通过嵌套获得隐式作用域
object Foo {

  trait Bar
implicit val bar = new Bar {
override def toString = "Foo`s Bar"
}
}
object Test extends App {
import Foo.Bar
class B extends Bar
def m(implicit bar: Bar) = println(bar.toString)
m
}

常用法

转换类型为期望的类型

scala> val i: Int = 3.5
<console>:7: error: type mismatch;
found : Double(3.5)
required: Int
val i: Int = 3.5
^
scala> implicit def double2Int(d: Double) = d.toInt
warning: there was one feature warning; re-run with -feature for details
double2Int: (d: Double)Int scala> val i: Int = 3.5
i: Int = 3

当我们尝试把一个带有精度的数字复制给Int类型时,编译器会给出编译错误,因为类型不匹配。当我们创建了一个double to int的隐式转换之后编译正常通过。还有一种情况是与新类型的操作。

  case class Rational(n: Int, d: Int) {
def +(r: Rational) = Rational(n + r.n, d + r.d)
} implicit def int2Rational(v: Int) = Rational(v, 1) Rational(1, 1) + Rational(1, 1) 1 + Rational(1, 1)

模拟新的语法

比如scala中的arrow(->)语法就是一个隐式转换

  implicit final class ArrowAssoc[A](private val self: A) extends AnyVal {
@inline def -> [B](y: B): Tuple2[A, B] = Tuple2(self, y)
def →[B](y: B): Tuple2[A, B] = ->(y)
}

类型类

类型类是一种非常灵活的设计模式,可以把类型的定义和行为进行分离,让扩展类行为变得非常方便。

 @implicitNotFound("No member of type class NumberLike in scope for ${T}")
trait Increasable[T] {
def inc(t: T): T
} object Increasable { implicit object IncreasableInt extends Increasable[Int] {
def inc(t: Int) = t + 1
} implicit object IncreasableString extends Increasable[String] {
def inc(t: String) = t + t
} } def inc[T: Increasable](list: List[T]) = {
val ev = implicitly[Increasable[T]]
list.map(ev.inc)
} inc(List(1, 2, 3))
inc(List("z", "a", "b"))

隐式参数

当我们在定义方法时,可以把最后一个参数列表标记为implicit,表示该组参数是隐式参数。一个方法只会有一个隐式参数列表,置于方法的最后一个参数列表。如果方法有多个隐式参数,只需一个implicit修饰即可。
当调用包含隐式参数的方法是,如果当前上下文中有合适的隐式值,则编译器会自动为改组参数填充合适的值。如果没有编译器会抛出异常。当然,标记为隐式参数的我们也可以手动为该参数添加默认值。def foo(n: Int)(implicit t1: String, t2: Double = 3.14)

隐式视图

隐式视图:把一种类型转换为其他的类型,转换后的新类型称为视图类型。隐式视图会用于以下两种场景:当传递给函数的参数与函数声明的类型不匹配时;

scala> def log(msg: String) = println(msg)
log: (msg: String)Unit scala> log("hello world")
hello world scala> log(123)
<console>:9: error: type mismatch;
found : Int(123)
required: String
log(123)
^ scala> implicit def int2String(i: Int): String = i.toString
warning: there was one feature warning; re-run with -feature for details
int2String: (i: Int)String scala> log(123)
123

当调用foo.bar,并且foo中并没有bar成员时(常用于丰富已有的类库)。


scala> :pas
// Entering paste mode (ctrl-D to finish)
class Strings(str: String) {
def compress = str.filter(_ != ' ').mkString("")
} implicit def strings(str: String): Strings = new Strings(str) // Exiting paste mode, now interpreting. warning: there was one feature warning; re-run with -feature for details
defined class Strings
strings: (str: String)Strings scala> " a b c d ".compress
res0: String = abcd

隐式类型

如果细心观察上边的compress的实现和文章开头lines的实现,这两段代码实现功能所采用的思路是类似的。但是,两端代码的实现形式是有区别的。lines的实现采用了scala中的隐式类型特性。隐式类型是scala提供的一种语法糖,隐式类型还是要转换为:类型+隐式视图的形式(也就是compress的形式)。

参考《Scala in depth》,《Scala 编程》。

Scala 隐式转换及应用的更多相关文章

  1. scala 隐式转换

    先参考这篇文章:http://www.jianshu.com/p/a344914de895 package com.test.scalaw.test /** * scala隐式转换 */ object ...

  2. Scala隐式转换

    package big.data.analyse.scala import java.io.File import scala.io.Source /** * 隐式转换 * Created by zh ...

  3. Scala隐式转换和隐式参数

    隐式转换 Scala提供的隐式转换和隐式参数功能,是非常有特色的功能.是Java等编程语言所没有的功能.它可以允许你手动指定,将某种类型的对象转换成其他类型的对象或者是给一个类增加方法.通过这些功能, ...

  4. 12、scala隐式转换与隐式参数

    一.隐式转换 1.介绍 Scala提供的隐式转换和隐式参数功能,是非常有特色的功能.是Java等编程语言所没有的功能.它可以允许你手动指定,将某种类型的对象转换成其他类型的对象. 通过这些功能,可以实 ...

  5. 15、Scala隐式转换和隐式参数

    1.隐式转换 2.使用隐式转换加强现有类型 3.隐式转换函数的作用域与导入 4.隐式转换发生时机 5.隐式参数 1.隐式转换 要实现隐式转换,只要程序可见的范围内定义隐式转换函数即可.Scala会自动 ...

  6. 9. Scala隐式转换和隐式值

    9.1 隐式转换 9.1.1 提出问题 先看一个案例演示,引出隐式转换的实际需要=>指定某些数据类型的相互转化 object boke_demo01 { def main(args: Array ...

  7. 了解下Scala隐式转换与柯理化

    之前有看过kafka源码,有很多implict声明的方法,当时看的一头雾水,今天趁着空闲,了解下scala 的隐式转换和柯理化相关语法知识. 隐式转换 需要类中的一个方法,但是这个类没有提供这样的一个 ...

  8. 实例理解scala 隐式转换(隐式值,隐式方法,隐式类)

    作用 简单说,隐式转换就是:当Scala编译器进行类型匹配时,如果找不到合适的候选,那么隐式转化提供了另外一种途径来告诉编译器如何将当前的类型转换成预期类型.话不多说,直接测试 ImplicitHel ...

  9. 记录: 一次解决整型溢出攻击(使用scala,隐式转换)

    最近项目遇到一次整型溢出攻击 有一个功能,玩家购买num个物品. 每个物品花费14货币. 客户端限制玩家只能购买 1-9999个该物品. 但是某玩家通过技术手段,获得了客户端的运行权限. 于是发送协议 ...

随机推荐

  1. Dubbo配置引发的一个问题--- Duplicate spring bean id

    1.原因 因项目业务需要,要调用RPC框架,项目原本已经依赖了很多RPC接口需要启动时加载,所以准备做成启动时不预加载. 就是在配置的时候加上check=false. 官方文档解释的作用,就是Dubb ...

  2. Hashtable源码解析

    Hashtable同样是基于哈希表实现的,同样每个元素是一个key-value对,其内部也是通过单链表解决冲突问题,容量不足(超过了阀值)时,同样会自动增长. Hashtable也是JDK1.0引入的 ...

  3. 四年级--python函数基础用法

    一.函数的定义,调用和返回值 1.1 语法 def 函数(参数一,参数二...): ''' 文档注释 ''' 代码逻辑一 代码逻辑二 .... return 返回值 1.2 定义函数的三种形式 说明: ...

  4. 安装ie时,报:此安装不支持您的操作系统的当前语言

    打开注册表(win的"运行"栏键入 regedit 再按 OK )的HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/Nls/ ...

  5. MySQL索引的使用

    1.创建和查看索引 所谓普通索引,就是在创建索引时,不附加任何限制条件(唯一.非空等限制).该类型的索引可以创建在任何数据类型的字段上. (1)创建表时,创建普通索引 语法: 例子: (2)在已经存在 ...

  6. java算法之超级丑数

    问题描述: 写一个程序来找第 n 个超级丑数. 超级丑数的定义是正整数并且所有的质数因子都在所给定的一个大小为 k 的质数集合内. 比如给你 4 个质数的集合 [2, 7, 13, 19], 那么 [ ...

  7. BOM和DOM的区别

    一.BOM和DOM之间的关系图 window对象是BOM的顶层(核心)对象,所有对象都是通过它延伸出来的,也可以称为window的子对象由于window是顶层对象,因此调用它的子对象时可以不显示的指明 ...

  8. 《与C语言相恋》

    第一章 <与C语言相恋> 目录: 1.1 C语言的诞生 1.2 相恋C语言的理由 1.3 相恋C语言的7个步骤 1.4 目标代码文件,可执行文件和库 1.5 本章小结 C语言的诞生 197 ...

  9. SpringMVC, Spring和Mybatis整合案例一

    一  准备工作 包括:spring(包括springmvc).mybatis.mybatis-spring整合包.数据库驱动.第三方连接池. 二  整合思路 Dao层: 1.SqlMapConfig. ...

  10. 设置ActiveMQ的访问密码

    1.设置ActiveMQ的访问密码,以提高ActiveMQ的安全性 2.在ActiveMQ的conf目录的activemq.xml中添加账号密码 2.1  添加的代码如下 <!-- 添加访问Ac ...