implicitly[Ordering[T]]

一般来说 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.toDouble

scala> 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) => x
    case x :: rest =>
      val maxRest = maxListOrdering(rest)
      if (ordering.gt(x, maxRest)) x
      else 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) => x
    case x :: rest =>
      val maxRest = maxListOrdering(rest)
      if (implicitly[Ordering[T]].gt(x, maxRest)) x
      else maxRest
  }

总结

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值