[关闭]
@xtccc 2016-03-09T10:34:45.000000Z 字数 8069 阅读 2166

implicit 关键字

给我写信
GitHub

此处输入图片的描述

Scala



假设一个函数的参数定义是类型A,但是我们给该函数提供的参数的类型是B。在编译时,如果编译器发现参数类型B不符合要求,会导致编译失败,那么它就会去寻找implict conversion。如果能找到有效的implicit conversion,那么就会调用它,将B类型的数据转化为A类型的数据。

Implicit conversion 应用的3个规则:
1. 只有用implicit修饰的函数会被认为是implicit conversion
2. implicit conversion必须以single identifier的形式位于scope内,或者位于source type/target type的companion object中
3. 只有一个implicit conversion会被应用:convert1(convert2(x))是不行的


Implicit conversion 应用的3种场合:
1. Implicit conversion to an expected type
2. Converting the receiver
3. Implicit Parameters


case 1: Implicit conversion to an expected type

  1. package cn.gridx.scala.example.implicits.conversions
  2. // implicit conversion `RMB2Dollar`本package内
  3. // 因此需要 import into this scope as a single identifier
  4. import other_conversions.OtherConversion.RMB2Dollar
  5. object Example {
  6. def main(args: Array[String]): Unit = {
  7. val d1 = new Dollar(2)
  8. val d2 = new Dollar(20)
  9. val r1 = new RMB(80)
  10. val r2 = new RMB(800)
  11. val euro= new Euro(15)
  12. println(r1.addDollar(r2))
  13. println(d1.addRMB(d2))
  14. println(euro.addEuro(r1))
  15. printDollar(2.0)
  16. // 无法通过编译,一次只能应用一个implicit conversion
  17. // 不能 double2Dollar(Dollar.Dollar2RMB(4.0))
  18. printRMB(4.0)
  19. }
  20. def printDollar(dollar: Dollar) = println(dollar)
  21. def printRMB(rmb: RMB) = println(rmb)
  22. implicit def double2Dollar(v: Double): Dollar = new Dollar(v)
  23. }
  24. --------------------------------------------------------------------------------------------
  25. package cn.gridx.scala.example.implicits.conversions;
  26. class Dollar(private val v: Double) {
  27. def getValue() :Double = v
  28. def toRMB(): Double = Dollar.rate2RMB*getValue
  29. def addRMB(rmb: RMB) :Dollar = new Dollar(v + RMB.rate2Dollar*rmb.getValue)
  30. override
  31. def toString() = s"""Dollar: ${getValue()},\tRMB: ${toRMB()}"""
  32. }
  33. object Dollar {
  34. def rate2RMB: Double = 8.0 // 美元兑人民币的汇率为1:8
  35. implicit def Dollar2RMB(dollar: Dollar): RMB = new RMB(Dollar.rate2RMB*dollar.getValue)
  36. }
  37. --------------------------------------------------------------------------------------------
  38. package cn.gridx.scala.example.implicits.conversions
  39. class RMB(private val v: Double) {
  40. def getValue(): Double = v
  41. def toDollar(): Double = RMB.rate2Dollar*v
  42. def addDollar(doll: Dollar): RMB = new RMB(getValue + Dollar.rate2RMB*doll.getValue)
  43. override
  44. def toString() = s"""RMB: ${v},\tDollar: ${toDollar()}"""
  45. }
  46. object RMB {
  47. def rate2Dollar: Double = 1/8.0 // 人民币兑美元的汇率为1/8
  48. // 这个implicit conversion可以位于`object RMB`中,也可以位于`object Euro`中
  49. implicit def rmb2Euro(rmb: RMB):Euro = new Euro(rmb.getValue()/Euro.rate2RMB)
  50. }
  51. --------------------------------------------------------------------------------------------
  52. package cn.gridx.scala.example.implicits.conversions
  53. class Euro(private val v: Double) {
  54. def getValue(): Double = v
  55. def addEuro(euro: Euro): Euro = new Euro(getValue() + euro.getValue())
  56. def toRMB(): Double = Euro.rate2RMB*getValue()
  57. override
  58. def toString(): String = s"""Euro: ${getValue()},\tRMB: ${toRMB()}"""
  59. }
  60. object Euro {
  61. def rate2RMB = 10.0
  62. }
  63. --------------------------------------------------------------------------------------------
  64. package cn.gridx.scala.example.implicits.conversions.other_conversions
  65. import cn.gridx.scala.example.implicits.conversions.{Dollar, RMB}
  66. object OtherConversion {
  67. implicit def RMB2Dollar(rmb: RMB): Dollar = new Dollar(rmb.getValue()*RMB.rate2Dollar)
  68. }


case 2: Converting the receiver

  1. package cn.gridx.scala.example.implicits.conversions
  2. object Example {
  3. def main(args: Array[String]): Unit = {
  4. val jpy = new JPY(1000)
  5. println(1000.0 + jpy) // `Double` -> `JPY`
  6. }
  7. }
  8. --------------------------------------------------------------------------------------------
  9. package cn.gridx.scala.example.implicits.conversions
  10. class JPY(private val v: Double) {
  11. def getValue(): Double = v
  12. def + (x: JPY): JPY = new JPY(x.getValue() + getValue())
  13. override
  14. def toString(): String = s"""JPY: ${getValue()},\tRMB: ${getValue()/JPY.rate2RMB}"""
  15. }
  16. object JPY {
  17. def rate2RMB: Double = 20.0
  18. implicit def double2JPY(x: Double): JPY = new JPY(x)
  19. }

Simulating New Syntax

在case 2 中,还可以引申出 simulating new syntax 这种用法。

我们可以如下创建一个Map实例:

  1. val map = Map(1 -> "One", 2 -> "Two", 3 -> "Three")

这里的操作符->是怎么来的?
它实际上是class ArrowAssoc中的一个方法,并且scala.Predef中定义了一个从AnyArrowAssco的implicit conversion。当遇到1时,1会从Any转换为ArrowAssco,进而可以使用操作符->

  1. object Predef extends LowPriorityImplicits {
  2. final class ArrowAssoc[A](val __leftOfArrow: A) extends AnyVal {
  3. @inline def -> [B](y: B): Tuple2[A, B] = Tuple2(__leftOfArrow, y)
  4. def →[B](y: B): Tuple2[A, B] = ->(y)
  5. }
  6. @inline implicit def any2ArrowAssoc[A](x: A): ArrowAssoc[A] = new ArrowAssoc(x)
  7. }

下面我们来验证,自己定义一个类似的操作符 -->>

  1. package cn.gridx.scala.example.implicits.conversions
  2. object Example {
  3. def main(args: Array[String]): Unit = {
  4. val map: Map[Int, String] = Map(1 -> "One", 2 -->> "Two", 3 -->> "Three")
  5. map.foreach(println)
  6. }
  7. final class MyArrowAssco[A](val _leftValue:A) extends AnyVal {
  8. def -->> [B](y:B): Tuple2[A, B] = Tuple2(_leftValue, y)
  9. }
  10. implicit def any2MyArrowAssco[A](x: A): MyArrowAssco[A] = new MyArrowAssco(x)
  11. }


Case 3: Implicit Parameter

在case 3中,编译器会将 someCall(a) 变为 someCall(a)(b) ,或者是将 new SomeClass(a) 变为 new SomeClass(a)(b) ,注意编译器添加的是最后一个curried paramater list,而不是最后一个parameter。

例1:implicit parameter list中只包含一个参数

  1. package cn.gridx.scala.example.implicits.conversions
  2. object Example {
  3. def main(args: Array[String]): Unit = {
  4. // bring `other_conversions.TaoPrompts.prompt1` into scope
  5. import other_conversions.TaoPrompts._
  6. Greeter.greet("Tao") // 隐式地使用提示符$
  7. Greeter.greet("Tao")(new Prompt(">")) // 显式地使用提示符>
  8. }
  9. }
  10. --------------------------------------------------------------------------------------------
  11. package cn.gridx.scala.example.implicits.conversions
  12. object Greeter {
  13. def greet(name: String)(implicit prompt: Prompt) = {
  14. println(s"""${prompt.msg} Welcom ${name}""")
  15. println(s"""${prompt.msg}""")
  16. }
  17. }
  18. --------------------------------------------------------------------------------------------
  19. package cn.gridx.scala.example.implicits.conversions
  20. class Prompt(val msg: String) { }
  21. --------------------------------------------------------------------------------------------
  22. package cn.gridx.scala.example.implicits.conversions.other_conversions
  23. import cn.gridx.scala.example.implicits.conversions.Prompt
  24. object TaoPrompts {
  25. implicit def prompt1 = new Prompt("$")
  26. }



这里的prompy其实只是一个String,为什么还要将其放在一个class中呢?因为implicit parameter是根据类型进行匹配的,所以自定义一个class就可以使得这个class很独特。


例2:implicit parameter list中包含多个参数

  1. package cn.gridx.scala.example.implicits.conversions
  2. object Example {
  3. def main(args: Array[String]): Unit = {
  4. // bring other_conversions.TaoPrompts.{prompt, drink} into scope
  5. import other_conversions.TaoPrefences._
  6. Greeter.greet("Tao") // 隐式地使用提示符$
  7. Greeter.greet("Tao")(new Prompt(">"), new Drink("Sprit"))
  8. }
  9. }
  10. --------------------------------------------------------------------------------------------
  11. package cn.gridx.scala.example.implicits.conversions
  12. object Greeter {
  13. def greet(name: String)(implicit prompt: Prompt, drink: Drink) = {
  14. println(
  15. s"""${prompt.msg} Welcom ${name},
  16. |we have a cup of ${drink.drink} for you !""".stripMargin)
  17. println(s"""${prompt.msg}""")
  18. }
  19. }
  20. --------------------------------------------------------------------------------------------
  21. package cn.gridx.scala.example.implicits.conversions
  22. class Prompt(val msg: String) {
  23. }
  24. --------------------------------------------------------------------------------------------
  25. package cn.gridx.scala.example.implicits.conversions
  26. class Drink(val drink: String) { }
  27. --------------------------------------------------------------------------------------------
  28. package cn.gridx.scala.example.implicits.conversions.other_conversions
  29. import cn.gridx.scala.example.implicits.conversions.{Drink, Prompt}
  30. object TaoPrefences {
  31. implicit def prompt = new Prompt("$")
  32. implicit def drink = new Drink("Coca-cola")
  33. }


例3:为显式定义的参数提供类型信息

假设我们想找出一个列表中最大的元素,很显然,这要求列表中的元素之间是可以比较大小的,因此我们在定义方法时限定列表中元素T必须是Ordered的子类。

  1. def maxListElem[T <: Ordered[T]](list: List[T]): T =
  2. list match {
  3. case List() => throw new IllegalArgumentException("Empty list")
  4. case List(x) => x
  5. case x::rest =>
  6. val maxRest = maxListElem(rest)
  7. if (maxRest > x) maxRest
  8. else x
  9. }

看起来不错,但是有个问题:该方法不适用于List[Int]参数,因为Int不是Ordered[T]的子类!


解决方法

方法1. 寻找一个T -> Ordered[T]的方法,对于任何类型T,将其变为类型Ordered[T]之后再进行比较大小。

  1. /**
  2. * 将T转化为Ordered[T]之后再进行比较
  3. */
  4. def maxListElem2[T](list: List[T])(implicit orderer: T => Ordered[T]): T =
  5. list match {
  6. case List() => throw new IllegalArgumentException("Empty list")
  7. case List(x) => x
  8. case x::rest =>
  9. val maxRest = maxListElem2(rest)(orderer)
  10. if (orderer(x) > maxRest) x // orderer(x)将x由T转化为类型Ordered[T]
  11. else maxRest
  12. }

这里,在运行时,系统会自动寻找一个实现了T -> Ordered[T]变换的隐式方法。

方法2. 寻找一个Ordering的实现类,然后利用这个类中的gt方法来进行大小的比较。

  1. /**
  2. * 使用Ordering[T]类的`gt`方法来进行比较
  3. */
  4. def maxListElem3[T](list: List[T])(implicit ordering: Ordering[T]): T =
  5. list match {
  6. case List() => throw new IllegalArgumentException("Empty list")
  7. case List(x) => x
  8. case x::rest =>
  9. val maxRest = maxListElem3(rest)(ordering)
  10. if (ordering.gt(x, maxRest)) x // 用`ordering.gt`比较x与maxRest
  11. else maxRest
  12. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注