隐式定义是指编译器为了修正类型错误而允许插入到程序中的定义。

举例:

正常情况下"120"/12显然会报错,因为 String 类并没有实现 / 这个方法,我们无法去决定 String 类的行为,这个时候就可以用上 implicit 关键字了。

使用 implicit 关键字定义函数。

implicit def String2Int(str: String) = {
str.toInt
}
print("120" / 12)

编译器一旦发现对于 String 类操作符 / 不可用,而 + 方法正好对应 Int 型的参数,且当前作用域存在 String 类型的隐式转换。

所以实际等价于print(String2Int("120") / 12)


隐式操作规则

作用域规则:插入的隐式转换必须以单一标识符的形式处于作用域中,或与转换的源或目标类型关联在一起。

Scope Rule: An inserted implicit conversion must be in scope as a single identifier, or be associated with the source or target type of the conversion.

Scala 编译器仅考虑处于作用域之内的隐式转换。可以使用 import 关键字访问其它库的隐式转换。

编译器还将在源类型或转化的期望目标类型的伴生对象中寻找隐式定义。

case class Euro(num: Int)

object Dollar {
implicit def EuroToDollar(euro: Euro): Dollar = Dollar(2 * euro.num)
} case class Dollar(num:Int) {
def +(d: Dollar) = num + d.num
} print(Dollar(3) + Euro(10)) // 23

无歧义规则:隐式转换唯有在不存在其它可插入转换的前提下才能插入。

Non-Ambiguity Rule: An implicit conversion is only inserted if there is no other possible conversion to insert.

implicit def String2Int(str: String) = {
str.toInt
}
implicit def String2Double(str: String) = {
str.toDouble
}
print("120" / 12)

上面的程序会导致编译错误。可以想象,如果不禁止这种操作势必会导致可读性的下降。

单一调用原则:只会尝试一个隐式操作。

One-at-a-time Rule: Only one implicit is tried.

编译器不会对某个变量执行多次的隐式转换。

显式操作先行原则:若编写的代码类型检查无误,则不会尝试任何隐式操作。

Explicits-First Rule: Whenever code type checks as it is written, no implicits are attempted.

implicit def String2Int(str: String) = {
str.toInt
} print("120" + 12) // 12012

120并没有被转换为 Int 类型,而是 12 被转换成了 String 类型。

也就是说编译器并不会优先考虑我们定义的隐式操作。

命名隐式转换

Naming an implicit conversion.

object MyConversions {
implicit def String2Int(str: String) = str.toInt
implicit def Double2Int(num: Double) = num.toInt
}
import MyConversions.Double2Int
val x:Int = 12.0
print(x)

这样可以很方便的在当前作用域内引入我们需要的隐式转换。

隐式转换

与新类型的交互操作

现在我们有一个 Person 类。

case class Person(name: String, age: Int) {
def +(x: Int) = age + x
def +(p: Person) = age + p.age
}

其中定义了 + 方法。

val person = Person("xiaohong", 1)
println(person + 1) // 2

但是反过来 println(1 + person) 就不行了。(整数类型显然没有合适的 + 方法)

这个时候定义从 Int 到 Person 的隐式转换就很方便了。

implicit def Add1(x: Int) = Person("Empty", x)
println(1 + person) // 2

当然也可以定义从 Person 到 Int 的隐式转换。

implicit def Add2(x: Person) = x.age
println(1 + person) // 2

但是如果我们同时定义了这两个函数,看看会发生什么?

implicit def Add1(x: Int) = Person("Empty", x)
implicit def Add2(x: Person) = x.age + 1 // 加以区分
println(1 + person) // 3

实际上 + 操作的原型是 1.+(person),person 作为参数。所以 person 被转化成了 Int 类型参与计算。

模拟新的语法

我们有很多种方法去创建一个 Map 对象。

var mp = Map(1 -> 2, 3 -> 4)
var mp2 = Map(Tuple2(1, 2), Tuple2(3, 4))
var mp3 = Map((1, 2), (3, 4))
var mp4 = Map(1 → 2, 3 → 4) print(mp == mp2)
print(mp == mp3)
print(mp2 == mp3)
print(mp == mp4)

到今天才知道 -> 符号是如何支持这种操作的。

去 Predef 库中找到 -> 的实现如下:

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)
}

并不是什么内建语法,其实就是基于隐式转换。

现在自己定义一个符号 -->

implicit class MyRange(start: Int) {
def -->(end: Int) = start to end
}
print((1 --> 10).sum) // 55

隐式参数

在一个方法的参数名前加上 implicit 关键字。

implicit val a = 2
implicit val b = "B" def fun(implicit x: Int, y:String) = {
x + y
} fun // 2B
fun(1, "A") // 1A

如果我们不提供相应的参数,那么方法会自动带入当前作用域内的带有 implicit 关键字的变量作为参数。编译器会根据类型去匹配。

总结

  隐式操作是功能强大、代码凝练的 Scala 特性。无论是标准库内还是其它开源的框架,都大量的使用了这一特性。但是它的频繁使用必然会导致代码可读性的大幅度降低,当然我针对的是库的使用者以及两个月后再看库的开发者。(越来越感觉 Scala 和 Java 走了两个极端)


参考

Scala 中的隐式转换和隐式参数的更多相关文章

  1. Spark基础-scala学习(八、隐式转换与隐式参数)

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

  2. Scala学习之路 (八)Scala的隐式转换和隐式参数

    一.概念 Scala 2.10引入了一种叫做隐式类的新特性.隐式类指的是用implicit关键字修饰的类.在对应的作用域内,带有这个关键字的类的主构造函数可用于隐式转换. 隐式转换和隐式参数是Scal ...

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

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

  4. Scala基础:闭包、柯里化、隐式转换和隐式参数

    闭包,和js中的闭包一样,返回值依赖于声明在函数外部的一个或多个变量,那么这个函数就是闭包函数. val i: Int = 20 //函数func的方法体中使用了在func外部定义的变量 那func就 ...

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

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

  6. Scala入门到精通——第十九节 隐式转换与隐式參数(二)

    作者:摇摆少年梦 配套视频地址:http://www.xuetuwuyou.com/course/12 本节主要内容 隐式參数中的隐式转换 函数中隐式參数使用概要 隐式转换问题梳理 1. 隐式參数中的 ...

  7. 02.Scala高级特性:第6节 高阶函数;第7节 隐式转换和隐式参数

    Scala高级特性 1.    课程目标 1.1.   目标一:深入理解高阶函数 1.2.   目标二:深入理解隐式转换 2.    高阶函数 2.1.   概念 Scala混合了面向对象和函数式的特 ...

  8. 大数据技术之_16_Scala学习_06_面向对象编程-高级+隐式转换和隐式值

    第八章 面向对象编程-高级8.1 静态属性和静态方法8.1.1 静态属性-提出问题8.1.2 基本介绍8.1.3 伴生对象的快速入门8.1.4 伴生对象的小结8.1.5 最佳实践-使用伴生对象解决小孩 ...

  9. scala中隐式转换之隐式转换调用类中本不存在的方法

    /** * Created by root * Description : 隐式转换调用类中本不存在的方法 */ class Person(name : String){ def getPersonN ...

随机推荐

  1. node.js 89行爬虫爬取智联招聘信息

    写在前面的话, .......写个P,直接上效果图.附上源码地址  github/lonhon ok,正文开始,先列出用到的和require的东西: node.js,这个是必须的 request,然发 ...

  2. 理解oracle中连接和会话

    详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcytp44 理解oracle中连接和会话 1.  概念不同:概念不同: 连接是指物 ...

  3. SQL Server 2008 开启数据库的远程连接

     转载: 陈萌_1016----有道云笔记 SQL Server 2008默认是不允许远程连接的,如果想要在本地用SSMS连接远程服务器上的SQL Server 2008,远程连接数据库.需要做两个部 ...

  4. RMA退货流程解决方案

    RMA退货流程解决方案 1.概述 在高科技制造业中有效地对产品退货进行控制和跟踪有很大的意义.对于一个产品成本从几元到几十万元的工业,管理退货流程的能力至关重要,缺乏跟踪和控制有可能导致上百万元的损失 ...

  5. 201521123084 《Java程序设计》第2周学习总结

    第2周作业-Java基本语法与类库 1. 本周学习总结 1.学会使用码云管理代码: 2.学会使用Eclipse关联jdk源代码,并查看对象的源代码: 3.学会String类和StringBuilder ...

  6. Beta阶段项目复审

    复审人:王李焕 六指神功:http://www.cnblogs.com/teamworkers/ wt.dll:http://www.cnblogs.com/TeamOf/ 六个核桃:http://w ...

  7. 团队作业9--beta版本测试报告及发布说明

    Beta版本测试报告 1.bug的分类 a.修复的bug 部分用户无法获取位置 e. 这个bug的确应该修复,但是没有时间在这个版本修复,延迟到下一个版本修复. 前端无法查看用户签到信息 2.场景测试 ...

  8. 201521123023《Java程序设计》第8周学习总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结集合与泛型相关内容. 1.2 选做:收集你认为有用的代码片段 2. 书面作业 本次作业题集集合 1.List中指定元素的删除(题目4 ...

  9. 201521123006 《Java程序设计》第4周学习总结

    1. 本周学习总结 1.1 尝试使用思维导图总结有关继承的知识点. 1.2 使用常规方法总结其他上课内容. 本周除了继承,我们还重点学习了多态. (1)多态性在于有相同的形态,却是不同的行为或者说是不 ...

  10. 201521123029《Java程序设计》第十周学习总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结异常与多线程相关内容. 2. 书面作业 本次PTA作业题集异常.多线程 1.finally 题目4-2 1.1 截图你的提交结果(出 ...