Scala 隐式转换和隐式参数

前言

Scala的implicit功能很强大,可以自动地给对象”添加一个属性”。 这里打上引号的原因是Scala内部进行编译的时候会自动加上隐式转换函数。

很多Scala开源框架内部都大量使用了implicit。因为implicit真的很强大,写得好的implicit可以让代码更优雅。但个人感觉implicit也有一些缺点,比如使用了implicit之后,看源码或者使用一些library的时候无法下手,因为你根本不知道作者哪里写了implicit。这个也会对初学者造成一些困扰。

比如Scala中Option就有一个implicit可以将Option转换成Iterable:

val list = List(1, 2)
val map = Map(1 -> 11, 2 -> 22, 3 -> 33)

val newList = list.flatMap {
    num => map.get(num) // map.get方法返回的是Option,可以被隐式转换成Iterable
} 

以下是implicit的一个小例子。

比如以下一个例子,定义一个Int类型的变量num,但是赋值给了一个Double类型的数值。这时候就会编译错误:

val num: Int = 3.5 // Compile Error

但是我们加了一个隐式转换之后,就没问题了:

implicit def double2Int(d: Double) = d.toInt

val num: Int = 3.5 // 3, 这段代码会被编译成 val num: Int = double2Int(3.5)

隐式转换规则

标记规则(Marking Rule)

任何变量,函数或者对象都可以用implicit这个关键字进行标记,表示可以进行隐式转换。

implicit def intToString(x: Int) = x.toString

编译器可能会将x + y 转换成 convert(x) + y 如果convert被标记成implicit。

作用域规则(Scope Rule)

在一个作用域内,一个隐式转换必须是一个唯一的标识。

比如说MyUtils这个object里有很多隐式转换。x + y 不会使用MyUtils里的隐式转换。 除非import进来。 import MyUtils._

Scala编译器还能在companion class中去找companion object中定义的隐式转换。

object Player {
    implicit def getClub(player: Player): Club = Club(player.clubName)
}

class Player(val name: String, val age: Int, val clubName: String) {

}

val p = new Player("costa", 27, "Chelsea")

println(p.welcome) // Chelsea welcome you here!
println(p.playerNum) // 21

一次编译只隐式转换一次(One-at-a-time Rule)

Scala不会把 x + y 转换成 convert1(convert2(x)) + y

隐式转换类型

隐式转换成正确的类型

这种类型是Scala编译器对隐式转换的第一选择。 比如说编译器看到一个类型的X的数据,但是需要一个类型为Y的数据,那么就会去找把X类型转换成Y类型的隐式转换。

本文一开始的double2Int方法就是这种类型的隐式转换。

implicit def double2Int(d: Double) = d.toInt

val num: Int = 3.5 // 3

当编译器发现变量num是个Int类型,并且用Double类型给它赋值的时候,会报错。 但是在报错之前,编译器会查找Double => Int的隐式转换。然后发现了double2Int这个隐式转换函数。于是就使用了隐式转换。

方法调用的隐式转换

比如这段代码 obj.doSomeThing。 比如obj对象没有doSomeThing这个方法,编译器会会去查找拥有doSomeThing方法的类型,并且看obj类型是否有隐式转换成有doSomeThing类型的函数。有的话就是将obj对象隐式转换成拥有doSomeThing方法的对象。

以下是一个例子:

case class Person(name: String, age: Int) {
    def +(num: Int) = age + num
    def +(p: Person) = age + p.age
  }

val person = Person("format", 99)
println(person + 1) // 100
//  println(1 + person)  报错,因为Int的+方法没有有Person参数的重载方法

implicit def personAddAge(x: Int) = Person("unknown", x)

println(1 + person) // 100

有了隐式转换方法之后,编译器检查 1 + person 表达式,发现Int的+方法没有有Person参数的重载方法。在放弃之前查看是否有将Int类型的对象转换成以Person为参数的+方法的隐式转换函数,于是找到了,然后就进行了隐式转换。

Scala的Predef中也使用了方法调用的隐式转换。

Map(1 -> 11, 2 -> 22)

上面这段Map中的参数是个二元元组。 Int没有 -> 方法。 但是在Predef中定义了:

implicit final class ArrowAssoc[A](private val self: A) extends AnyVal {
    @inline def -> [B](y: B): Tuple2[A, B] = Tuple2(self, y)
    def →[B](y: B): Tuple2[A, B] = ->(y)
}

隐式参数

隐式参数的意义是当方法需要多个参数的时候,可以定义一些隐式参数,这些隐式参数可以被自动加到方法填充的参数里,而不必手填充。

def implicitParamFunc(name: String)(implicit tiger: Tiger, lion: Lion): Unit = {
    println(name + " have a tiget and a lion, their names are: " + tiger.name + ", " + lion.name)
}

object Zoo {
    implicit val tiger = Tiger("tiger1")
    implicit val lion = Lion("lion1")
}

import Zoo._

implicitParamFunc("format")

上面这个代码中implicitParamFunc中的第二个参数定义成了隐式参数。

然后在Zoo对象里定义了两个隐式变量,import进来之后,调用implicitParamFunc方法的时候这两个变量被自动填充到了参数里。

这里需要注意的是不仅仅方法中的参数需要被定义成隐式参数,对应的隐式参数的变量也需要被定义成隐式变量。

其他

对象中的隐式转换可以只import自己需要的。

object MyUtils {
    implicit def a ...
    implicit def b ...
}

import MyUtils.a

隐式转换修饰符implicit可以修饰class,method,变量,object。

修饰方法和变量的隐式转换本文已经介绍过,就不继续说了。

修饰class的隐式转换,它的作用跟修饰method的隐式转换类似:

implicit class RangeMarker(val start: Int) {
    def -->(end: Int) = start to end
}

1 --> 10 // Range(1, 10)

上段代码可以改造成使用Value Class完成类的隐式转换:

implicit class RangeMaker(start: Int) extends AnyVal {
    def -->(end: Int) = start to end
}

修饰object的隐式转换:

trait Calculate[T] {
    def add(x: T, y: T): T
}

implicit object IntCal extends Calculate[Int] {
    def add(x: Int, y: Int): Int = x + y
}

implicit object ListCal extends Calculate[List[Int]] {
    def add(x: List[Int], y: List[Int]): List[Int] = x ::: y
}

def implicitObjMethod[T](x: T, y: T)(implicit cal: Calculate[T]): Unit = {
    println(x + " + " + y + " = " + cal.add(x, y))
}

implicitObjMethod(1, 2) // 1 + 2 = 3
implicitObjMethod(List(1, 2), List(3, 4)) // List(1, 2) + List(3, 4) = List(1, 2, 3, 4)
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
本文作者:Format
原文链接: http://fangjian0423.github.io/2015/12/20/scala-implicit/
版权归作者所有,转载请注明出处