在我看来,Implicit 做的事情也是 Scala 主要做的事情,那就是代码压缩,减少模块代码,talk is cheap,先用一个实例来了解一下 Implicit 的作用。

一个栗子

马上就到情人节了,你会如何表达你的爱意呢?

让我们做一些准备工作,首先要有一个恋人的接口,包含一个表达爱意的函数sendLove

trait Lover {def sendLove(love: Love) {}
}

然后再来一个爱意接口,爱不能总是在心口难开,而是应当有所行动,所以有一个行动函数 takeActionevent是触发爱的事件,这里当然是情人节这件大事了

trait Love {val event: Eventval show = takeAction(event)def takeAction(event: Event)
}

今年的情人节很尴尬,正好在过年的前一天,所以这一天很多恋人可能都各呆各家各找各妈了,所以这里应当是异地恋,吼吼吼吼让你们虐狗

class RemoteLover extends Lover {}val lover: Lover = new RemoteLover

OK,准备工作完成,情人节到了,异地恋们来表达一波爱意吧!

// without implicit
lover.sendLove(new Love {val event = Event("Valentine is coming!")def takeAction(event: Event) = {println("Buy a gift!")}}
)

爱你的方式多种多样,但爱你的心却永远不变。可能是送一件礼物聊表心意,也可能是飞奔到你身边给一个拥抱。我并不是在装情圣,我只是想说明,上面代码中,会改变的其实只有这行代码

println("Buy a gift!")

Love及其实现的函数 takeAction可以说是固定的模块化代码,如果能缩减成下面这个样子那就太棒了

lover.sendLove((_: Event) => println("Give a hug!")
)

看一下发生了什么。函数 sendLove所需要的参数是 Love对象:

new Love {val event = Event("Valentine is coming!")def takeAction(event: Event) = {println("Buy a gift!")}
}

但是缩减后其对应的参数变成了一个函数:

(_: Event) => println("Give a hug!")

不行啊!这样搞参数类型不匹配啊!这时候就该想到类型转换了。如下是一个超简单的例子:

"abc" + 123

这里 123 是 Int 转换成了 String,参考这个思路,试着将函数转化成Love

implicit def function2Love(f: Event => Unit) = new Love {val event = Event("Valentine is coming!")def takeAction(event: Event): Unit = f(event)
}

这时原来的代码就可以写成:

lover.sendLove(function2Love((_: Event) => println("Give a hug!"))
)

这里我们明确指定函数 function2Love进行类型转换,注意上面 function2Love的前面使用了 implicit 关键字,这个关键字的作用就是可以隐式将一种类型转换为另一种类型,而不需要明确调用,如下:

// with implicit
import Lover.function2Lovelover.sendLove((_: Event) => println("Give a hug!")
)

在这里具体发生了什么呢?首先sendLove需要的参数类型是Love,但是这里的参数却是一个函数。当编译器发现代码中存在不匹配的类型的时候,它不会立即报错,而是选择先抢救一下,抢救的方式就是在当前代码域中找一下有没有 implicit 修饰的代码,然后找到了function2Love这个隐式转换函数,代码编译通过。

以上就是 Implicit 的一个入门例子,全部代码见 GitHub

一般来说 Implicit 主要用在两个方面:隐式转换和隐式参数,下面分别进行讲解

隐式转换

隐式转换到某个期望类型

任何时候编译器发现了类型 X,但是却需要类型 Y 的时候,它就会寻找一个可以把 X 转换成 Y 的隐式函数。

例如正常情况下一个 Double 类型是不能转换成 Int 类型,但是我们可以定义一个隐式函数来实现:

scala> implicit def doubleToInt(x: Double) = x.toInt
doubleToInt: (x: Double)Int

虽然我在这里举了一个这样的例子,但是这种从 Double 转到 Int 的方式是不推荐的,因为会丢失精度。相反从 Int 转到 Double 就是正常的,而且其底层实现就是用的隐式转换。Scala 有一个 scala.Predef对象,默认引入到每一个程序中,就包含了类似这种从“小”数类型向“大”数类型的隐式转换:

implicit def int2double(x: Int): Double = x.toDoublescala> val x: Double = 3
x: Double = 3.0

隐式类

隐式类是 Scala2.10 新增的,为了更方便地写包装器类。有以下几个要点:

  1. 隐式类不能是 case class,并且构造器必须只有一个参数
  2. 一个隐式类必须在其他 object,class,trait 的内部
  3. 对于一个隐式类,编译器会自动生成一个隐式转换函数,该函数会产生一个隐式类对象

例如一个矩形类 Rectangle

case class Rectangle(width: Int, height: Int)

新建一个矩形需要这样:

scala> val rec = Rectangle(3, 4)
rec: Rectangle = Rectangle(3,4)

这还是有点麻烦的,如果我们想通过 3 x 4 这种方式就新建一个对象那该怎么办呢,这个时候就要用到隐式类了:

implicit class RectangleMaker(width: Int) {def x(height: Int) = Rectangle(width, height)
}

我们再来建立一个矩形:

scala> val easyRec = 3 x 4
easyRec: Rectangle = Rectangle(3,4)

这看起来就简单直接多了嘛,那么它是怎么实现的呢?前面我们说了,对于隐式类,编译器会自动生成一个隐式转换函数,如下:

// Automatically generated
implicit def RectangleMaker(width: Int) = new RectangleMaker(width)

所以当编译器看到 3 x 4 的时候,首先发现 3 这个 Int 没有 x 函数,然后就找隐式转换,通过上面的隐式转换函数生成了一个 RectangleMaker(3),然后调用 RectangleMaker的 x 函数,就产生了一个矩形 Rectangle(3,4)

隐式参数

下面要开始谈隐式参数了,这也是我们最常用到的,隐式参数并不是用于类型转换的,更多的是为了减少代码量,要点如下:

  • 隐式参数是指类或者函数所对应的参数列表,如下面 implicit 所在的参数列表
implicit class Greet(name: String)(implicit hello: Hello, world: World)
  • implicit 放在参数列表的最前面,不管有几个参数,它们全部都是隐式的
  • 定义了隐式参数以后,我们新建一个类或者调用一个函数的时候就可以省略该参数列表
val greet = new Greet("Jack")
  • 省略参数列表并不是不需要参数列表,我们需要使用 implicit 关键字定义出隐式参数列表中的所有变量,然后使用 import 导入
implicit val hello = new Hello
implicit val world = new Wrold

上面就是一些基本点,狗年春节马上就要来了,就让我们以此为例来写一个回家的 Demo 吧

case class Remote(address: String)
case class Home(address: String)object Transportation {def transport(name: String)(implicit remote: Remote, home: Home) = {println(s"To celebrate Spring Festival, go from $remote to $home, by $name")}
}object Address {implicit val remote = new Remote("Shanghai")implicit val home = new Home("Shanxi")
}

这里定义了起始地址Remote Home,虽然其本质是 String 类型,但是对于隐式参数来说最好还是定义一个专门的类,因为如果直接使用 String 作为隐式变量,由于其太过于普遍编译器可能不太容易找到,或者和其他隐式变量混淆,而专用的类就不用担心这些。
还有一个表示交通方式的函数transport,该函数里面用到了隐式参数,最后Address对象定义了我们需要的隐式变量。

然后,准备回家喽!

object GoHome extends App {import Address._Transportation.transport("airplane")
}

结果如下:

To celebrate Spring Festival, go from Remote(Shanghai) to Home(Shanxi), by airplane.

上下文绑定

了解了隐式参数以后,我们可以再看一个有趣的语法糖:上下文绑定 context bound

这里我用《Programming in Scala》中的一个例子来讲解,要求找出一个 List 中所有元素的最大值,首先我们看一下常规的实现了隐式参数的写法:

// with implicit
def maxListOrdering[T](elements: List[T])(implicit ordering: Ordering[T]): T =elements match {case List() =>throw new IllegalArgumentException("empty lists!")case List(x) => xcase x :: rest =>val maxRest = maxListOrdering(rest)if (ordering.gt(x, maxRest)) xelse maxRest}

这个函数的参数有两个,一个是 T 组成的 List,另一个是隐式参数 Ordering,对于一些常见的 T 类型,例如 Int 或者 String,它们都有一个默认的 Ordering 实现,所以不需要定义其隐式变量就可以实现排序:

println(maxListOrdering(List(1, 5, 10, 3)))

在上面代码中有一行用到了 ordering 参数:

if (ordering.gt(x, maxRest)) x

这里 ordering 就是 Ordering[T] 的一个对象,事实上 Scala 标准库中提供了一个方法让编译器可以自己寻找到一个类的隐式变量:

def implicitly[T](implicit t: T) = t

例如一个类型 Foo,调用 implicitly[Foo]会产生什么结果呢?首先编译器会寻找 Foo 的隐式对象,找到以后调用这个对象的 implicitly 方法,然后返回该对象,所以通过 implicitly[Foo]可以返回 Foo 的隐式对象,对应到我们上面举的例子,ordering 是 Ordering[T] 的一个隐式对象,事实上我们也可以通过 implicitly[Ordering[T]]来表示

if (implicitly[Ordering[T]].gt(x, maxRest)) x

那么现在看来,ordering 既然可以用 implicitly[Ordering[T]] 来代替,那么也就可以省略掉这个参数名了,所谓的上下文绑定就是做这个事情的,语法是 [T: Ordering],它主要做了两件事:第一,引入了一个参数类型 T;第二,增加了一个隐式参数 Ordering[T]。
与之很类似的一个语法是 [T <: Ordering[T]],这个的意思是 T 就是一个 Ordering[T]。
现在再来看一下引入上下文绑定后的排序代码:

// context bound
def maxListOrdering[T: Ordering](elements: List[T])(implicit ordering: Ordering[T]): T =elements match {case List() =>throw new IllegalArgumentException("empty lists!")case List(x) => xcase x :: rest =>val maxRest = maxListOrdering(rest)if (implicitly[Ordering[T]].gt(x, maxRest)) xelse maxRest}

总结

implicit 使用起来还是很灵活的,可以将 implicit 关键字标记在变量、函数、类或者对象的定义中。而且记住想要用到隐式,一定要先使用 import 引入 implicit 代码(当然在同一个代码域例如同一个对象下面不需要)。但是也需要注意,如果太过于频繁地使用 implicit,代码可读性就会很低,所以在使用隐式转换之前,先看一看能否用其他方式例如继承、重载来实现,如果都不行并且代码仍然很冗余,那么就可以试试用 implicit 来解决。

Scala之——Implicit 详解相关推荐

  1. Scala Implicit 详解

    Implicit 是 Scala 中一个很重要的特性,开始学习 Scala 之前一直以为它和 Java 差不多,然而真的看一些 Scala 的源码时却发现并没有想象中那么简单,所以准备写几篇文章来详解 ...

  2. scala学习笔记(十三):implicit 详解

    implicit 可分为三种 隐式参数 隐式转换类型 隐式调用函数 1.隐式参数 implicit参数都是定义在方法最后,修饰implicit表示该组参数是隐式参数.一个方法只会有一个隐式参数列表,置 ...

  3. scala 隐式详解(implicit关键字)

    掌握implicit的用法是阅读spark源码的基础,也是学习scala其它的开源框架的关键,implicit 可分为: 隐式参数 隐式转换类型 隐式调用函数 1.隐式参数 当我们在定义方法时,可以把 ...

  4. scala yield入门详解

    概念 可以遍历集合并对集合元素处理产生新集合,新集合和原有集合类型相同. (range的不同) Array,List,Set,Range 本质 语法糖 用法 scala> val s=Array ...

  5. Scala语言基础详解,并在IDEA中安装Scala插件

    Scala起源 Scala是一门多范式的编程语言,一种类似java的编程语言,设计初衷是实现可伸缩的语言.并集成面向对象编程和函数式编程的各种特性. Scala语言的特点: Scala是面向对象的:S ...

  6. c++关键词explicit和implicit详解

    explicit:该构造函数是显示的. implicit:该构造函数是隐式的(默认) 说白了就在在构造函数只有一个时能不能使用=号直接赋值. 如果是显示的会被阻止,在编译时无法自动转为对象. 如果时隐 ...

  7. Scala系列8:函数式编程之map,flatten,flatmap的使用详解

    0.Scala函数式编程 我们将来使用Spark/Flink的大量业务代码都会使用到函数式编程.下面这些事开发中常用的函数式编程.注意这些函数都是操作 Scala 集合的,一般会进行两类操作:转换操作 ...

  8. spark最新源码下载并导入到开发环境下助推高质量代码(Scala IDEA for Eclipse和IntelliJ IDEA皆适用)(以spark2.2.0源码包为例)(图文详解)...

    不多说,直接上干货! 前言   其实啊,无论你是初学者还是具备了有一定spark编程经验,都需要对spark源码足够重视起来. 本人,肺腑之己见,想要成为大数据的大牛和顶尖专家,多结合源码和操练编程. ...

  9. Scala进阶之路-面向对象编程之类的成员详解

    Scala进阶之路-面向对象编程之类的成员详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.Scala中的object对象及apply方法 1>.scala 单例对象 ...

最新文章

  1. 用GAN还原语义标注图!还能手动改细节(附论文、代码)
  2. 【Linux系统编程】Linux文件操作
  3. 网络分流器-网络分流器-网络安全评估探讨
  4. IntelliJ idea 中使用Git
  5. Java中对象和引用的理解
  6. Papers with Code 2020 全年回顾
  7. zhongdexing-pro
  8. thinkphp中的session的使用和理解!
  9. socket编程 TCP 粘包和半包 的问题及解决办法
  10. creo工程图模板_Creo工程图的优越性总结(仅供参考),来自网友与君共享
  11. 时尚服装行业挑战及软件机遇分享 -- 许鹏
  12. 微信小程序——仿写京东购物商城带源码
  13. matlab里面box on啥意思,image – Matlab图片中的Box on和axis坐标
  14. PointNet论文翻译
  15. 设计一个学生类Student
  16. 2018年上半年软考信息安全工程师上午真题及答案解析
  17. 网络攻击与防御基本概念
  18. 通过Burp以及自定义的Sqlmap Tamper进行二次SQL注入
  19. H5响应式网站制作那些事
  20. 基于K-Means的文本聚类

热门文章

  1. 嘿嘿又一数据库!redis数据库!redis部署、持久化及性能管理!
  2. 无线路由器几种加密方式
  3. 我想拿到Offer(一)
  4. 小波变换在图像压缩方面的分析与应用(Matlab代码)
  5. 鼠标优化工具Mos Mac中文版
  6. linux中.tar文件怎么解压
  7. pushstate和popstate的实现原理
  8. MySQL是如何实现事务的——锁机制、MVVC详解
  9. 开源的fortran编译器LFortran
  10. 查询网站使用什么web服务器