扒一扒scala隐式转换

操作规则:

1.作用域,scala编译器将仅考虑作用域之内的隐式转换,要使用某种隐式操作,你必须以单一标识符的形式(一种情况例外)将其带入作用域之内。例如:

object TestImplicit {
  implicit def doubleToInt(x: Double) = x.toInt
}
object test {
  def main(args: Array[String]): Unit = {
    import layne.impilcit.TestImplicit._//以单一标识符引进doubleToInt的隐式转换
    val i: Int = 2.3
  }
}

单一标识符有一个外例,编译器还将在源类型和目标类型的伴生对象中寻找隐式定义。

2.无歧义,比方说 x+y需要用到隐式转换,但是在作用域内却找到不止一个隐式定义与之匹配,将不能编译通过。

object TestImplicit {
  implicit def doubleToInt(x: Double) = x.toInt
}
object TestImplicit2{
  implicit def doubleToInt(x: Double) = x.toInt
}
object test {
  def main(args: Array[String]): Unit = {
    import layne.impilcit.TestImplicit._
    import layne.impilcit.TestImplicit2._
    val i: Int = 2.3//能匹配多个隐式定义,编译将报错
  }
}

用在何处:

1.转换为期望类型:
在交互模式下直接定义

scala> val i: Int = 3.5
<console>:7: error: type mismatch;
 found : Double(3.5)
 required: Int
  val i: Int = 3.5
  ^

将报类型错误
我们定义隐式转换doubleToInt后再重复之前的操作:

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

scala> val i: Int = 3.5
i: Int = 3

我们看到Double已经被隐式的转换为我们期望的Int类型。
2.转换接收者
如果你编写了some.doSomething()的代码,而 some中没有定义doSomething方法的话,编译器在放弃之前会尝试插入转换代码。

我们通过一个有理数例子来看看这种转换:

package layne.blog.impilcit

/**
  * Created by layne on 08/05/17.
  */
class Rational(val numerator: Int, val denominator: Int) {

  require(denominator != 0) //分母为零

  def +(rational: Rational): Rational = {
    /**
      * 最大公约数
      *
      * @param numerator   分子
      * @param denominator 分母
      * @return 最大公约数
      */
    def divisor(numerator: Int, denominator: Int): Int = {
      val s = if (numerator > denominator) denominator else numerator
      (1 to s).reverse.find(it => numerator % it == 0 && denominator % it == 0).getOrElse(1)
    }
    val d = divisor(rational.numerator * this.denominator + this.numerator * rational.denominator,
      this.denominator * rational.denominator)
    Rational((rational.numerator * this.denominator + this.numerator * rational.denominator) / d, this.denominator * rational.denominator / d)
  }

  override def toString: String = this.numerator + "/" + this.denominator
}

object Rational {

  def apply(numerator: Int, denominator: Int): Rational = new Rational(numerator, denominator)

//隐式转换整数为Rational
  implicit def intToRational(num: Int) = new Rational(num, 1)
}

object TestMain extends App {
  val oneHalf =  Rational(1, 2)
  val rational = 2 + oneHalf
  println(rational)
}

输出结果为5/2
如果没有隐式转换 们将很难实现 如:2 + oneHalf 的表达式计算,Int类型本身并没有签名为: +(rational: Rational): Rational的方法。而通过隐式转换方法调用者,很轻松完成。

3.作为隐式参数

调用一个带有隐式参数的函数,如果我们不提供显式参数,编译器不会马上报错,在报错之前,编译器会尝试在作用域内寻找单一隐式值(用implicit关键字修饰的参数),用来补充缺失参数:
下例中首先定义一个带隐式参数的函数,我们尝试调用,作用域内没有可以用来补充缺失参数的隐式值

scala> def test(implicit t: String): Unit = println(t)
test: (implicit t: String)Unit
scala> test
<console>:9: error: could not find implicit value for parameter t: String
              test
              ^

添加一个隐式值,再调用

scala> implicit val s:String = "test"
s: String = test

scala> test
test
scala> test("test")
test

作用域内有多个匹配的隐式值:

scala> implicit val s1:String = "test"
s1: String = test

scala> test
<console>:11: error: ambiguous implicit values:
 both value s of type => String
 and value s1 of type => String
 match expected type String
              test
              ^

再来看一个稍微复杂一点,用来选出集合内的最大元素的例子:

package layne.blog.impilcit

import scala.concurrent.duration.Deadline

/**
  * Created by layne on 09/05/17.
  */
object MaxElement {

  def maxElement[T <: Ordered[T]](list: List[T]): T = list match {
    case List() => throw new RuntimeException("空集合")
    case List(x) => x
    case x :: rest => val maxRest = maxElement(rest)
      if (x > maxRest) x else maxRest
  }
  def maxElement2[T](list: List[T])(implicit covert: T => Ordered[T]): T = list match {
    case List() => throw new RuntimeException("空集合")
    case List(x) => x
    case x :: rest => val maxRest: T = maxElement2(rest)(covert)
      if (covert(x) > maxRest) x else maxRest
  }

  def maxElement3[T <% Ordered[T]](list: List[T]): T = list match {
    case List() => throw new RuntimeException("空集合")
    case List(x) => x
    case x :: rest => val maxRest: T = maxElement3(rest)
      if (x > maxRest) x else maxRest
  }

  def main(args: Array[String]): Unit = {
    val e1 = Deadline.now
    Thread.sleep(1000)
    val e2 = Deadline.now
    Thread.sleep(1000)
    val e3 = Deadline.now
    println(maxElement(List(e1, e2, e3)))
    println(maxElement2(List("e1", "e2", "e3")))
    println(maxElement2(List(1, 2, 3)))
  }
}

为了能方便的使用 “>”,”<”等操作符,我们让集合内的元素T必须是Ordered[T] 的子类,
但是这样做,我们的大多数基本类型都用不了了,因为scala的基本类型不是Ordered[T]的直接子类。
例如我们如果调用:println(maxElement(List("e1", "e2", "e3"))), 编译时就会有如下错误

Error:(39, 13) inferred type arguments [String] do not conform to method maxElement's type parameter bounds [T <: Ordered[T]]
    println(maxElement(List("e1", "e2", "e3")))
Error:(39, 28) type mismatch;
 found   : List[String]
 required: List[T]
    println(maxElement(List("e1", "e2", "e3")))

于是我们想到了隐式参数,maxElement2,因为implicit修饰的是整个参数列表,所以我们需要用柯里化将显示参数和隐式参数分开,scala标准库提供了许多T => Ordered[T]的隐式转换,如:定义在scala.Predef中的implicit def augmentString(x: String): StringOps = new StringOps(x) 让我们能将maxElement2运用在String类型上
因此,我们能把maxElement2,用在许多类型上。

maxElement2也是第三个函数maxElement3中视图界定的原理,不熟悉的小伙伴们可以仔细揣摩一下。

3.隐式类

所谓隐式类就是用implicit 关键字修饰的类(隐式类与样例类互斥即一个类不能同时被implicit和case修饰)
例如:

package layne.blog.impilcit

/**
  * Created by layne on 09/05/17.
  */
object ImplicitClass {
  implicit class ImplString(string: String) {
    def sayHellow = println("hello " + string)
  }

}
object TestMain{
  def main(args: Array[String]): Unit = {
    def main(args: Array[String]): Unit = {
      import layne.blog.impilcit.ImplicitClass._
      "jack".sayHellow
    }
  }
}

当 “jack”.sayHellow 时,scala 编译器不会立马报错,而检查当前作用域有没有 用implicit 修饰的,同时可以将String作为参数的构造器,并且具有方法sayHellow的类,经过查找 发现 ImplString 符合要求
利用隐式类 ImplString 执行sayHellow 方法

有了这样的隐式类,我们还能模拟新语法,例如我们创建Map对象:

Map(1 -> "test1",2 -> "test2")

” -> ” 这不是scala内建的语法,只是定义在scala.Predef中的类ArrowAssoc中的方法,感兴趣的同学可以去研究下。

隐式操作的频繁使用会让代码变得晦涩难懂,但是我们要学习scala,乃至查看用scala编写的开源项目如spark,kafka,我们必须熟练这个特性。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值