隐式转换

隐式转换和隐式参数是Scala中两个非常强大的功能,利用隐式转换和隐式参数,可以提供类库,对类库的使用者隐匿掉具体的细节。

Scala会根据隐式转换函数的签名,在程序中使用到隐式转换函数接收的参数类型定义的对象时,会自动将其传入隐式转换函数,转换为另外一种类型的对象并返回,这就是“隐式转换”。

  • 首先得有一个隐式转换函数

  • 使用到隐式转换函数接收的参数类型定义的对象

  • Scala自动传入隐式转换函数,并完成对象的类型转换

隐式转换需要使用implicit关键字。

使用Scala的隐式转换有一定的限制:

  • implicit关键字只能用来修饰方法、变量、参数

  • 隐式转换的函数只在当前范围内才有效。如果隐式转换不在当前范围内定义,那么必须通过import语句将其导入

Spark源码中有大量的隐式转换和隐式参数,因此必须掌握隐式机制。

隐式转换函数

Scala的隐式转换最核心的就是定义隐式转换函数,即implicit conversion function。

定义的隐式转换函数,只要在编写的程序内引入,就会被Scala自动使用。

隐式转换函数由Scala自动调用,通常建议将隐式转换函数的名称命名为“one2one”的形式。

示例1:下面代码中定义了一个隐式函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package lagou.cn.part10

class Num{}
class RichNum(num:Num){
def rich(): Unit ={
println("hello implicit!")
}
}

object ImplicitDemo {
//定义一个隐式转换函数,命名要符合one2one的格式
implicit def num2RichNum(num:Num):RichNum={
new RichNum(num)
}

def main(args: Array[String]): Unit = {
val num=new Num
//num类型的对象没有rich方法,但是Scala编译器会查找当前范围内是否有隐式转换函数,
//然后将其转换成RichNum类型,这样就可以访问rich方法了
num.rich()
}
}

示例2:导入隐式函数

1
2
3
4
5
package test.implicitdemo

object Int2String {
implicit def int2String(num: Int):String = num.toString
}

下面代码中调用了String类型的length方法,Int类型本身没有length方法,但是在可用范围内定义了可以把Int转换为String的隐式函数int2String,因此函数编译通过并运行出正确的结果。

此示例中隐式函数的定义必须定义在使用之前,否则编译报错。

1
2
3
4
5
6
7
import test.implicitdemo.Int2String._

object ImplicitTest {
def main(args: Array[String]): Unit = {
println(20.length)
}
}

通过import test.implicitdemo.Int2String._,将Int2StringTest内部的成员导入到相应的作用域内,否则无法调用隐式函数。

要实现隐式转换,只要在程序可见的范围内定义隐式转换函数即可,Scala会自动使用隐式转换函数。

隐式转换函数与普通函数的语法区别就是,要以implicit开头,而且最好要定义函数返回类型。

隐式转换案例:特殊售票窗口(只接受特殊人群买票,比如学生、老人等),其他人不能在特殊售票窗口买票。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package lagou.cn.part10

class SpecialPerson(var name: String)

class Older(var name: String)

class Student(var name: String)

class Worker(var name: String)

object ImplicitDemoTwo {
def buySpecialTickWindow(person: SpecialPerson): Unit = {
if (person != null) {
println(person.name + "购买了一张特殊票!")
} else {
println("你不是特殊人群,不能在此购票!")
}
}

//定义一个隐式转换函数
implicit def any2SpecialPerson(any: Any): SpecialPerson = {
any match {
case any: Older => new SpecialPerson(any.asInstanceOf[Older].name)
case any: Student => new SpecialPerson(any.asInstanceOf[Student].name)
case _ => null
}
}

def main(args: Array[String]): Unit = {
val stu = new Student("jacky")
val older = new Older("old man")
val worker = new Worker("lisi")

buySpecialTickWindow(stu)
buySpecialTickWindow(older)
buySpecialTickWindow(worker)
}
}

隐式参数和隐式值

在函数定义的时候,支持在最后一组参数中使用 implicit ,表明这是一组隐式参数。在调用该函数的时候,可以不用传递隐式参数,而编译器会自动寻找一个 implicit 标记过的合适的值作为参数。

Scala编译器会在两个范围内查找:

  • 当前作用域内可见的val或var定义隐式变量

  • 隐式参数类型的伴生对象内隐式值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package lagou.cn.part10

object DoublyDemo {
def print(num:Double)(implicit fmt:String): Unit ={
println(fmt format(num))
}

def main(args: Array[String]): Unit = {
print(3.245)("%.1f")

//定义一个隐式变量
implicit val printFmt="%.3f"
print(3.24)
}
}