1. 程式人生 > >Scala基礎-隱式轉換

Scala基礎-隱式轉換

作為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。

——————————————————————————————

後記:

本文文初時的一些猜測是便於理解,並不正確。隱式轉換主要的目的是讓一個沒有該方法的物件隱式轉化為具有這個方法的物件,以便於我們可以呼叫本不屬於該物件的方法;隱式引數是我們在故意漏寫引數的時候讓編譯器可以在上下文中尋找隱式引數來充當我們漏寫的引數。我想這就是隱式引數和隱式轉化最為概括的一個理解。 ---------------------