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

Spark基礎--Scala隱式轉換

1、隱式轉換
通常在處理兩個開發時完全不知道對方才能在的軟體或型別庫時非常有用,他們各自都有自己的方式描述某個概念,而這個概念本質上是描述同一件事。隱式轉換可以減少一個型別顯示轉換成另一個型別的需要。
2、隱式轉換規則
隱式定義指可以允許編譯器插入程式解決型別錯誤的定義。比如如果a+b是不能編譯,那麼編譯器會把它改成convert(a)+b的形式,其中convert就是某種轉換。如果經過convert修復能夠支援+這個動作,就說這個程式是可以修復的。
標記規則:只有標記為implicit定義才可用。,關鍵字implicit用來標記哪些宣告可以被編譯器用作隱式定義。可以用implicit來標記任何變數、函式或物件定義。下面有一個隱式函式定義:
implicit def intToString(x:Int):String=x.toString
scala指揮考慮那些在作用域內的隱式轉換。所以必須把隱式轉換定義引入到當前作用域才可用。

我們把隱式轉換說的簡單一點,每當編譯器看到A型別而它需要的是B型別,編譯器就會查詢一個能把A轉換到B的隱式轉換。
例如雙精度浮點型不能被用作整數。因為會丟失精度。

scala> val ini:Int=3.14
<console>:15: error: type mismatch;
 found   : Double(3.14)
 required: Int
       val ini:Int=3.14
                   ^

如果想讓他通過,我們可以寫一個隱式轉換。
scala> implicit def doubleToInt(a:Double):Int=a.toInt
warning: there was one feature warning; re-run with -feature for details
doubleToInt: (a: Double)Int
這個轉換已經成功但是編譯器告訴我們一個警告。首先應用隱式轉換
scala> val i:Int=3.14
i: Int = 3
現在我們的編譯能夠正常通過。編譯器查詢到一個double型別,也就是3.14.但是這裡需要的是一個Int,這是一個型別的錯誤,但是編譯器在放棄之前會查詢一個從Double到Int的隱式轉換。他找到了我們定義的doubleToInt。進行了型別轉換,程式設計了Int也就是3。
那麼val i:Int=3.14被程式設計了val i:Int=doubleToInt(3.14)
這裡我們並沒有要求進行這樣的轉換。而是通過把doubleToInt作為標記符納入到作用域,將他標記為可用的隱式轉換。這樣編譯器就會在需要Double轉換成Int時自動使用。
這裡我們應該注意到以上的那個警告,警告是在提醒我們應用這個隱式轉換會有風險,這裡的風險就是精度丟失的問題。Double型別轉換成Int會丟失資料的精度。這在程式編寫中是不允許的。如果是Int向Double型別轉換則是更講的通。scala就是這麼應用的。scala程式引入的物件都定義了那些從小的數值型別向更大的數值型別的隱式轉換。例如scala的Predef有如下的轉換的定:

implicit def Byte2byte(x: java.lang.Byte): Byte = x.byteValue
implicit def Short2short(x: java.lang.Short): Short= x.shortValue
implicit def Character2char(x: java.lang.Character): Char= x.charValue
implicit def Integer2int(x: java.lang.Integer): Int= x.intValue
implicit def Long2long(x: java.lang.Long): Long= x.longValue
implicit def Float2float(x: java.lang.Float): Float= x.floatValue
implicit def Double2double(x: java.lang.Double): Double= x.doubleValue
implicit def Boolean2boolean(x:java.lang.Boolean):Boolean = x.booleanValue

這些是java的相應型別向scala型別的隱式轉換。
還有
implicit def int2double(x:Int):Double=x.toDouble
這樣Int型別向double型別的轉換,如果我們使用瞭如下定義:

scala> val i=3
i: Int = 3

我們會看到預設型別是Int,同樣我們定義雙精度型別最開始的型別依然是Int,這時編譯器遇到了錯誤,編譯器就會去查詢有沒有Int向Double的轉換,在scala內找到了相應的轉換所以,跳轉到了Double型別

scala> val i=3.14
i: Double = 3.14

隱式轉換還可以隱式的呼叫轉換後類的方法,例如我們以前的Rational

class Rational(n:Int,d:Int){
	require(d!=0)
	val numer:Int=n
	val demon:Int=d
override def toString=numer+"/"+demon
   def +(that:Rational):Rational={
			new Rational(
					numer*that.demon+that.numer*demon,
					demon*that.demon
					)
	}
}

我們應用的時候

scala> val oneHalf=new Rational(1,2)
oneHalf: Rational = 1/2

scala> val oneHalf1=new Rational(1,2)
oneHalf1: Rational = 1/2
scala> oneHalf + oneHalf1
res113: Rational = 4/4

結果是正確的,但是當我們直接用數字去加一個Rational的時候就有問題了。

scala> 1+ oneHaflf
<console>:18: error: not found: value oneHaflf
       1+ oneHaflf

這個問題是因為編譯器會檢查 1+ oneHaflf,1是Int型別,雖然裡面有眾多的+的方法,但是沒有一個是接收Rational引數的,所以報了型別錯誤。
這裡我們只要做一個隱式轉換即可,只要把Int型別轉換到Rational型別即可。

scala> implicit def intToRational(x:Int)=new Rational(x,1)
warning: there was one feature warning; re-run with -feature for details
intToRational: (x: Int)Rational

我們再次使用剛剛的運算,就可以得到正確的結果

scala> 1+oneHalf
res115: Rational = 3/2

隱式類:隱式類是以implicit打頭的類,對於這樣的類編譯器會自動生成一個從類的構造方法引數到類的本身的隱式轉換。例如:
定義一個類

scala> case class Rectangl(width:Int,height:Int)
defined class Rectangl

scala> implicit class RectangleMake(width:Int){
     | def make(height:Int)=Rectangl(width,height)}
defined class RectangleMake

scala> 3 make 4
res116: Rectangl = Rectangl(3,4)

工作過程:因為3並沒有make的方法,編譯器會查詢隱式轉換的可能,他將找到RectangleMake的轉換類,而RectangleMake確實有一個名為make的方法,編譯器就會呼叫這個方法。
來看兩個示例程式:
隱式轉換:

class SpPerson(val name:String)
class Student(val name:String)
class Older(val name:String)
class Teacher(val name:String)

object Implaction{
	implicit def spPerson(obj:Object):SpPerson={//傳Object-任何 型別
			if(obj.getClass==classOf[Student]){
				//通過object獲取類名,是否等於Student
				val stu=obj.asInstanceOf[Student]
						//例項化Student
						new SpPerson(stu.name)
				//返回SpPerson類
			}
			else if(obj.getClass==classOf[Older]){
				val older=obj.asInstanceOf[Older]
						new SpPerson(older.name)
			}else{
				Nil
			}
	}
	var ticket=0
			def buySpPerson(person:SpPerson)={
		ticket=ticket+1
				"T-"+ticket
	}
}
object test{
  	import Implaction._
  def main(args: Array[String]) = {
		val stu1=new Student("gong")
		println(buySpPerson(stu1))
		// 發現Student,但是要求是SpPerson,返回查詢看是否有隱式轉換
		val older=new Older("oldgongbaoying")
		println(buySpPerson(older))
		val teacher=new Teacher("ss")
		println(buySpPerson(teacher))
		//因為Teacher沒有隱式轉換,返回的是Nil所以會卡住不動了。
	}
}

執行結果:
T-1
T-2
程式解析,首先定義了四個類SpPerson表示特殊人群,特殊人群是可以購買車站的半價票,還有Student、Older、Teacher三個類,我們假定Student和Older是特殊人群,而Teacher不是,那麼我們要做的就是當遇到Student和Older是就能自動呼叫SpPerson購買半票的行為,而Teacher或者其他的型別則不能。
接下來定義一個Implaction單例物件,然後定義了一個隱式轉換的方法,這個方法的引數是obj:Object,因為我們要傳不同的型別進來檢查,所有就使用了所有類的超類。然後進行檢查if(obj.getClass==classOf[Student]),如果是Student類,那麼就先例項化Student類,然後把例項化後的類的引數傳遞給SpPerson作為例項化的引數。從而實現轉化。同理older也是一樣的檢查。注意最後是一個else,裡面給了Nil,因為我們的spPerson方法返回的是SpPerson,不加else就會產生型別不匹配的問題,我們通過所有型別的子型別作為返回值是最好的選擇,所以else內是Nil。

通過本例子,能夠自己寫隱式轉換,
超人變身:

class Man(val name:String)
class SuperMan(val name:String) {
	def emltiaser = println("emit a pingpang ball!")
}
object Implaction02{
	implicit def manToSuperMan(man:Man):SuperMan=
			new SuperMan(man.name)
	def main(args: Array[String]): Unit = {
		val dijia=new Man("dijia")
		dijia.emltiaser
	}
}

這是一個男人變超人的例子,這個例子和上一個不同,首先定義了兩個型別Man和SuperMan,裡面都有一個引數,然後定義了一個單例物件Implaction02,單例物件裡做了一個隱式轉換manToSuperMan,這裡方法給的引數是Man型別,返回型別是SuperMan,返回的方式是直接例項化new SuperMan(man.name)。
在主方法內定義了一個Man例項,讓這個例項直接呼叫SuperMan的方法,如果成功就代表能夠進行轉化。

隱式引數:
編譯器有時會將Call(a)轉化為Call(a)(b)或者new Sume(a)轉化為new Sume(a)(b)。隱式引數提供的是真個最後一組柯里化的引數列表,不僅僅是一個函式呼叫。一個簡單的例子

class PreferredP(val preferenrence:String)
def greet(name:String)(implicit promt:PreferredP)={
    println("Welcome "+name +".System is PreferredP")
    println("I'm "+promt)
}

呼叫,給兩個引數,第二個引數是PreferredP,實現了引數的自動呼叫。

scala> greet("scala")(pre)
Welcome scala.System is PreferredP
I'm test

我們再定義一個隱式轉化,並且放在一個單例物件中

scala> object JoesPrefs{
      implicit val prompt=new PreferredP("Scala")
      }
defined object JoesPrefs

scala> greet("joe")
<console>:25: error: could not find implicit value for parameter promt: Prefe
dP
       greet("joe")

會發生錯誤,告訴我們引數給個數不對,我們要呼叫就要把隱式轉換引入到作用域內。

scala> import JoesPrefs._
import JoesPrefs._

scala> greet("joe")
Welcome joe.System is PreferredP
I'm Scala

能夠正常呼叫。

以上過程我們再次進行系統的梳理,梳理後再次加入幾個小功能,梳理後的程式如下。

class PreferredPrompt(val pro:String)
class PreferredDrink(val prod:String)
 object Greeter{
  def greet(name:String)(implicit prompt:PreferredPrompt,
      drink:PreferredDrink)={
      println("Welcome "+name+"The book is ready")
      println("while you read")
      println("why are like"+drink.prod+"?")
      println(prompt.pro)
  }
}
object JoesPrefs{
  implicit val prompt=new PreferredPrompt("Yes master")
  implicit val drink=new PreferredDrink("tea")
}

我們定義了兩個類PreferredPrompt和PreferredDrink,定義了單例物件Greeter,裡面有一個隱式轉換greet後面的一個隱式引數,是由兩個型別組成的,在方法體內調引數的兩個屬性。此時我們雖然實現了隱式轉化,但是greet的第二個引數必須給兩個引數才能執行。我們現在想實現只給一個引數就能執行greet。這個方式是通過第二個單例物件JoesPrefs實現。這裡面添加了兩個隱式引數,分別對應了greet的prompt和drink,當遇到這兩個引數型別時,就會呼叫隱式的型別轉換。
同樣直接呼叫一個引數的就會出現問題:

scala> Greeter.greet("scala")
<console>:31: error: could not find implicit value for parameter prompt: Pref
edPrompt
       Greeter.greet("scala")

我們需要把隱式引數轉換引入到當前的作用域內:

scala> import JoesPrefs._
import JoesPrefs._
再次呼叫產生如下結果:
scala> Greeter.greet("scala")
Welcome scalaThe book is ready
while you read
why are liketea?
Yes master

想了解更多大資料共享資料歡迎加群:947967114