Scala隐式转换与隐式参数详解

隐式转换是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隐式转换何时发生,这个问题有必要讨论一下,在一般情况下有三张情况:

  1. 调用某个函数,但是传入函数中的参数类型不匹配的时候会发生隐式转换,如上面讲解的售票案例。
  2. 使用某个类型的对象调用一个方法,但是该对象中没有定义该方法,这时候会 发生隐式转换,比如上面所说的加强现有类型就是这种情况。
  3. 使用某一个类型的对象调用 一个方法,虽然该类型 定义了该 方法,但是传入该方法中的参数类型与方法需要的参数类型不匹配时会发生隐式转换。

对于第三种情况 我们可以看看下面这个案例,和第一种情况类似:


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框架的源码中也涉及到很多隐式转换,因此这些知识点我们有必要掌握。如有问题欢饮留言讨论。


你可能感兴趣的:(Scala)