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

猜你喜欢

转载自blog.csdn.net/AiMaEdu/article/details/83857021