隐式转换是Scala中一种非常有特色的功能,是其他编程语言所不具有的,它允许你手动指定,将某种类型对象转换为另一种类型对象。通过这个功能可是实现 更加灵活强大的功能。
转发写标明原文地址:原文地址
Scala的隐式转换最为核心的就是定义隐式函数,即implicit conversion function,定了隐式函数,在程序的一定作用域中scala会 自动调用。Scala会根据隐式函数的签名,在程序中使用到隐式函数参数定义的类型对象时,会自动调用隐式函数将其传入隐式函数,转换为另一种类型对象并返回,这就是scala的“隐式转换”。
隐式函数的名字可以由用户自己随便指定,scala内部会自动调用,但有时候不在 程序的作用域使用隐式转换的时候,需要手动 导入隐式函数,因此通常情况我们将隐式函数名定义为Object2otherObject的形式。隐式转换不仅对于scala是重要的一个知识点,学习大数据的工程也要掌握,因此大数据计算框架Spark是由scala编写,并且其中也大量使用了隐式转换,因此掌握隐式转换也有利用于我们阅读spark源码。
隐式转换
定义隐式转换很简单,只要在程序可见范围内定义隐式函数即可(一般指一个文件内)。隐式函数的定义与普通函数唯一的区别就是隐式函数开头需要用implicit关键字声明,并且函数必须定义返回值。
下面,我们使用一个案例来讲述隐式函数的定义与使用。
我们模拟售票,一个售票窗口针对特殊人群进行售票,比如老人学生等有优惠,我们定义三个类,SpecialPerson,Student和Older,售票这个函数中传入的参数是SpecialPerson类型的对象,在Java 中如果这三个类是继承关系,后两个继承前者,那么在售票函数中传入参数并不会有什么错误,可以如果这三个类之间没有任何关系,那肯定就会报错了,在Scala中隐式转换就解决了这个问题,我们可以定义一个隐式函数进行类型自动转换,无需用户手动调用,定义之后系统自动调用,下面看看程序如何编写:
class SpecialPerson(val name:String)
class Student(val name:String)
class Older(val name:String)
implicit def object2SpecialPerson(obj:Object):SpecialPerson={
if(obj.getClass==classOf[Student]){
val stu=obj.asInstanceOf[Student]
new SpecialPerson(stu.name)
}else if(obj.getClass==classOf[Older]){
val older=obj.asInstanceOf[Older]
new SpecialPerson(older.name)
}else Nil
}
var ticketNumber=0
def buySpecialTicket(p:SpecialPerson)={
ticketNumber+=1
"T-"+ticketNumber
}
程序如上所示。下面我们可以定义Student类和Older类传入函数中,看看会不会报错。
scala> val shinelon=new Student("shinelon")
shinelon: Student = Student@907f2b7
scala> val jack=new Older("jack")
jack: Older = Older@775594f2
scala> buySpecialTicket(shinelon)
res1: String = T-1
scala> buySpecialTicket(shinelon)
res2: String = T-2
scala> buySpecialTicket(jack)
res3: String = T-3
结果显示并且没有报错,因为我们定义了隐式函数,当在buySpecialTicket函数中传入Student和Older对象的时候Scala发现类型不匹配会将这些对象传入隐式函数object2SpecialPerson中进行类型转换,最后该函数返回SpecialPerson类型对象,因此调入buySpecialTicket成功。
隐式转换加强扩展现有类型
什么是隐式转换加强扩展现有类型呢?就是说一个类调用另一个类中它自己没有定义的方法时,隐式转换可以自动将该类转换为另一类从而扩展自己的功能,然后调用 该方法。
在Java中,如果想要一个类调用另一个类中方法(之间没有任何引用关系与集成关系),似乎是不可能的。但是scala的隐式转换可以实现这功能。下面我们来看一个小案例:
class Man(val name:String)
class Superman(val name:String){
def emitLaser=println("emit a laster")
}
implicit def man2superman(man:Man):Superman=new Superman(man.name)
下面定义一个Man对象调用Superman类中 方法emitLaser:
scala> val shinelon=new Man("shinelon")
shinelon: Man = Man@2b556bb2
scala> shinelon.emitLaser
emit a laster
可以看到函数成功被调用,说明隐式转换发生了。
导入隐式转换函数
Scala中默认会 使用两种隐式转换,一种是源类型,或者目标类型的伴生对象内的隐式转换函数;一种是当前程序作用域内用唯一标识符表示的隐式转换函数。
如果隐式转换函数不在上面的两种情况之中,就需要手动导入另一个包中隐式函数,比如import test._。通常情况下我们只在需要的隐式转换的地方定义隐式函数,这样也可以缩小隐式函数的作用域。
隐式转换的发生时机
关于scala隐式转换何时发生,这个问题有必要讨论一下,在一般情况下有三张情况:
对于第三种情况 我们可以看看下面这个案例,和第一种情况类似:
class SpecialPerson(val name:String)
class Student(val name:String)
class Older(val name:String)
implicit def object2SpecialPerson(obj:Object):SpecialPerson={
if(obj.getClass==classOf[Student]){
val stu=obj.asInstanceOf[Student]
new SpecialPerson(stu.name)
}else if(obj.getClass==classOf[Older]){
val older=obj.asInstanceOf[Older]
new SpecialPerson(older.name)
}else Nil
}
class TicketHouse{
var ticketNumber=0
def buySpecialTicket(p:SpecialPerson)={
ticketNumber+=1
"T-"+ticketNumber
}
}
下面调用函数就会发生隐式转换:
scala> val shinelon=new Student("shinelon")
shinelon: Student = Student@21c7208d
scala> val house=new TicketHouse
house: TicketHouse = TicketHouse@71d8cfe7
scala> house.buySpecialTicket(shinelon)
res6: String = T-1
隐式参数
如果接触过Spring的读者都知道,Spring的核心之一就是依赖注入,也就是我们在Spring容器中定义一个Bean,那么我们可以在函数中通过参数传递的方式注入我们所需要的类型,注入过程也是Spring框架自动完成的,Scala中的隐式参数也和Spring的依赖注入类似。所谓隐式参数,指的是在函数或者方法中,定义使用implicit修饰的参数,此时scala会尝试找到一个指定类型,使用implicit修饰的对象,即隐式值,注入到函数参数中函数体使用。
Scala会在两个范围内查找:一种是当前作用域内可见的var或val定义的隐式变量;一种是隐式参数类型伴生对象内的隐式值。
下面看一个案例:
class SignPen{
def write(content:String)=println(content)
}
implicit val signPen=new SignPen
def signForExam(name:String)(implicit signPen:SignPen){
signPen.write(name+"come to exam in time.")
}
在函数signForExam中我们调用SignPen对象write函数,因为在程序中定义了该对象隐式值,因此在调用该函数时它会自动注入该对象,因此我们就可以成功调用函数执行输出结果了。
scala> signForExam("shinelon")
shinelon come to exam in time.
至此,本篇文章就介绍完了Scala的隐式转换与隐式参数,在Spark框架的源码中也涉及到很多隐式转换,因此这些知识点我们有必要掌握。如有问题欢饮留言讨论。