一般来说 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 来解决。

implicitly[Ordering[T]]相关推荐

  1. Ordering使用

    object Test02 { def main(args: Array[String]): Unit = { val user1 = new User1("jack", 10) ...

  2. Spark的RDD分区器

    RDD 分区器 基本介绍 Spark 目前支持Hash 分区.Range 分区和用户自定义分区.Hash 分区为当前的默认分区.分区器直接决定了RDD 中分区的个数.RDD 中每条数据经过Shuffl ...

  3. [scala-spark]12. RDD行动操作

    first first返回RDD中的第一个元素,不排序. scala> var rdd1 = sc.makeRDD(Array(("A","1"),(&q ...

  4. Spark分区器HashPartitioner和RangePartitioner代码详解

    转载: https://www.iteblog.com/archives/1522.html 在Spark中分区器直接决定了RDD中分区的个数:也决定了RDD中每条数据经过Shuffle过程属于哪个分 ...

  5. spark:sortByKey实现二次排序

    最近在项目中遇到二次排序的需求,和平常开发spark的application一样,开始查看API,编码,调试,验证结果.由于之前对spark的API使用过,知道API中的sortByKey()可以自定 ...

  6. [Spark] - HashPartitioner RangePartitioner 区别

    Spark RDD的宽依赖中存在Shuffle过程,Spark的Shuffle过程同MapReduce,也依赖于Partitioner数据分区器,Partitioner类的代码依赖结构主要如下所示: ...

  7. Spark: sortBy和sortByKey函数详解

    在很多应用场景都需要对结果数据进行排序,Spark中有时也不例外.在Spark中存在两种对RDD进行排序的函数,分别是 sortBy和sortByKey函数.sortBy是对标准的RDD进行排序,它是 ...

  8. Scala零基础教学【61-80】

    第61讲:Scala中隐式参数与隐式转换的联合使用实战详解及其在Spark中的应用源码解析 第62讲:Scala中上下文界定内幕中的隐式参数与隐式参数的实战详解及其在Spark中的应用源码解析 /** ...

  9. Scala 上下文界定

    基本介绍 与 view bounds 一样 context bounds(上下文界定)也是隐式参数的语法糖. 为语法上的方便, 引入了"上下文界定"这个概念 就是设置一个隐式值,到 ...

最新文章

  1. MongoDB的update和set的用法
  2. Java String类方法
  3. Go语言TCP Socket编程
  4. docker安装nginx并进行-v挂载
  5. hiho一下 第七周 Hihocoder #1043 : 完全背包
  6. ipython和python怎么用_如何使用IPython重新加载和自动加载?
  7. [vue] 怎么在vue中使用插件?
  8. 小米10首销战绩公布:嘴上说不买身体却很诚实
  9. 实数在java中的表示,java - 如何在Z3(Java)中从模型中获取实数值作为小数(双精度)? - SO中文参考 - www.soinside.com...
  10. 百度js选择器fox
  11. html、input隐藏内容占空间与隐藏内容不占空间
  12. Canto加速市场的发展,连接全球的金融衍生品市场
  13. ZigBee网络数据传递流程_基于ZigBee远程通信的水质监测系统设计
  14. .Net framework3.5装不上解决之道错误代码 0x800F0906、0x800F081F
  15. 《Chrome插件英雄榜》第88篇更新!知乎网页助手让网页版知乎更好用
  16. STM32——TIM1的TIM1_CH1N通道PWM初始化
  17. 02. 只允许使用QQ和微信 - 服务 ❀ 飞塔 (Fortinet6.0) 防火墙
  18. HTML第6章上机练习3(制作京东快报页面)
  19. Ubuntu16.04中python升级到3.6版本后Terminal打不开的解决方法
  20. 使用系统自带计算器进行二进制运算

热门文章

  1. make与sudo make的区别
  2. HistoryInFilm for mac(实用的电影分类视频软件)
  3. Millionaire (期望)
  4. 2020 给自己定个小目标
  5. 声纹识别之GMM-UBM系统框架简介
  6. 50页京东云·睿擎-打造企业数字化转型的敏捷引擎业务中台解决方案
  7. Modbus tcp转Profinet网关连接电动机保护测控在1200PLC配置案例
  8. CF——Technical Support
  9. 工业设计师不可不知的网站
  10. TF-IDF算法实现