作为scala的一个优秀的功能,也是困扰我许久的一个功能,今天尝试弄明白。
我们知道scala语言以简洁著称,几十行的java代码scala通常几行就可以搞定,为了达到这个特点,个人认为编译器会尝试尽可能多的做一些推导,比如:1,就默认为Int类型,a,就默认为String类型,因为我们通常都是这样用的,为什么非要每次都需要明确指出它的类型呢?你说我就认为1是String类型的“1”呢?那可以啊,你要用String 的类型拼接的时候,比如1+a,我给你自动识别成“1a”啊,你做1+1的时候我给你自动识别成2啊,又有什么不可以呢?
以上都是我个人为了想便于了解scala的隐式转换所做的一些思想准备,实际上可能不是这样的。那么我们现在可以了来了解网上经常说到scala隐式转换的那句话:
“隐式转换和参数,可以在编写Scala程序时故意漏掉一些信息,让编译器去尝试在编译期间自动推导出这些信息来,这种特性可以极大的减少代码量,忽略那些冗长,过于细节的代码。”
好像是这么回事哦,我写1,我就不写1:Int;写a,就不写a:String,我让编译器自己去给我推导这些信息。好了,我们现在弄明白隐式转换他要达到一个什么目的了,现在我们就可以来看看他是怎么使用的。
一、隐式参数
object Test7 {
def k1(x:Int,y:Int):Int=x+y
def k2(x:Int)(implicit y:Int=5):Int=x+y
def main(args: Array[String]): Unit = {
println(k1(2,3))
println(k2(5))
}
}
结果为:
5
10
k1可以理解,调用k2的时候,我们只给了一个参数,却也能得出结果,是因为在方法中隐式给了值。编译器在调用k2的时候发现只穿了一个参数,这时就会在上下文中寻找有没有隐式参数,如果有,就调用隐式参数。
object Test7 {
implicit var b:Int=10
def k2(x:Int)(implicit y:Int=5):Int=x+y
def main(args: Array[String]): Unit = {
println(k2(5))
}
}
结果为:
15
可以发现编译器会首先在上下文中寻找隐式参数,一找到符合的,就使用,不再去找参数列表中寻找。
object Test7 {
implicit var b:Double=10.0
def k2(x:Int)(implicit y:Int=5):Int=x+y
def main(args: Array[String]): Unit = {
println(k2(5))
}
}
结果为:
10
可以发现,隐式参数是以数据类型区分的。
object Test7 {
implicit var b:Int=10
implicit var c:Int=15
def k2(x:Int)(implicit y:Int=5):Int=x+y
def main(args: Array[String]): Unit = {
println(k2(5))
}
}
结果是:报错。
这里可以发现隐式参数不能算相同数据类型的,这样编译器无法确定你需要哪个。
总结:
1、当编译器发现该有参数的地方没有参数的时候,就会在上下文去寻找有没有隐式参数。
2、编译器会首先从上下文中寻找,一旦找到就使用该隐式值,如果找不到就到方法的参数列表中去寻找,直到找到为止。
3、隐式参数是按照数据类型区分,如果两个隐式参数数据类型相同,编译器会因为无法区分而报错。
4、隐式参数拿来就能用,所以隐式参数是声明在object中的。
二、隐式转化:
时机之一:当一个对象想要调用一个方法,但是这个对象又没有该方法,这时会触发隐式转换。编译器回去隐式方法里去找,看有没有这样一个隐式函数,把我这个对象转换为有这个方法的对象。如果我变成了这个对象后,不就有这个方法了吗?
class Man(val name:String){}
class SuperMan(val name:String){
def fly():Unit={
println("超人会飞")
}
}
object Test7 {
implicit def man2SuperMan(man: Man):SuperMan=new SuperMan("")
def main(args: Array[String]): Unit = {
val man=new Man("abc")
man.fly()
}
}
时机之二:当一个对象想要调用一个方法,这个对象确实也有该方法,但是传进去的参数类型不匹配,也会触发隐式转换。
class Worker(val name:String) {}
class Student(val name:String){} //特殊人
class Older(val name:String){} //特殊
class SpecialPerson(val name:String){}
class TichkerHouse{
/**
* 这个售票厅里面应该有一个方法,这个方法针对人群售票
* 这个售票窗口应该是面向 SpecialPerson 人群的
*
*/
def buyTichker(p:SpecialPerson): Unit ={
println("您好"+p.name+" 您买到票了!!")
}
}
object Test7{
implicit def object2SpecialPerson(obj:Object):SpecialPerson={
if(obj.getClass == classOf[Student]){
val student = obj.asInstanceOf[Student]
new SpecialPerson(student.name)
}else if(obj.getClass == classOf[Older]){
val older = obj.asInstanceOf[Older]
new SpecialPerson(older.name)
}else{
None
}
}
def main(args: Array[String]): Unit = {
val zhangwuji = new Student("zhangwuji")
val older = new Older("malaoshi")
val worker = new Worker("lilaoshi")
val house = new TichkerHouse()
house.buyTichker(worker)
}
}
个人看来用的最多是import别的类中的隐式方法:
import sqlContext.implicits._
用于将RDD转化为DataFrame的时候,我们大概的思路是这样的。
import sqlContext.implicits._
val df: DataFrame = sc.textFile("").map(_.split(",")).map(p => Person(p(0), p(1).trim.toInt)).toDF()
1、我们想把RDD转化成DataFrame,我们想到了toDF()方法
2、但是RDD中并不存在toDF()方法,那么我们想调用toDF这个方法该怎么办?
3、看看toDF()是哪个类中的方法?发现是DatasetHolder类中的方法
def toDF(): DataFrame = ds.toDF()
4、如果能将RDD隐式转化为DatasetHolder那不就可以了吗?
5、恰好在SQLContext中有一个内部类
object implicits extends SQLImplicits with Serializable
而在SQLImplictis中有:
implicit def rddToDatasetHolder[T : Encoder](rdd: RDD[T]): DatasetHolder[T] = {
DatasetHolder(_sqlContext.createDataset(rdd))
}
找到了将rdd转化为DatasetHolder的隐式转换方法。
所以,我们就可以顺利地将RDD转化为DataFrame。
——————————————————————————————
后记:
本文文初时的一些猜测是便于理解,并不正确。隐式转换主要的目的是让一个没有该方法的对象隐式转化为具有这个方法的对象,以便于我们可以调用本不属于该对象的方法;隐式参数是我们在故意漏写参数的时候让编译器可以在上下文中寻找隐式参数来充当我们漏写的参数。我想这就是隐式参数和隐式转化最为概括的一个理解。