*一.隐式转换*
使用方式:
1.将方法或变量标记为implicit
2.将方法的参数列表标记为implicit
3.将类标记为implicit
限制:
1.implicit关键字只能用来修饰方法、变量(参数)和伴随对象。
2.隐式转换的方法(变量和伴随对象)在当前范围内才有效。如果隐式转换不在当前范围内定义(比如定义在另一个类中或包含在某个对象中),那么必须通过import语句将其导入。
3.源类型或目标类型(包括源类型和目标类型的类型变量的类型)的伴随对象中隐式转换函数(或变量,伴随对象)不需要显示导入。
1.1 隐式参数函数
不是手动调用,是Scala使用时自动被调用的,将值从一种类型转换成另一种类型。
scala> class Person(val name:String)
defined class Person
scala> class Engineer(val name: String, val salary: Double)
defined class Engineer
scala> new Person("Spark").code
:12: error: value code is not a member of Person
new Person("Spark").code
//Person 没有code方法,无法调用
scala> class Engineer(val name: String, val salary: Double){
| def code = println("Coding ....")
| }
defined class Engineer
scala> def toCode(p: Person){
| p.code
| }
:12: error: value code is not a member of Person
p.code
//报错,Person为code方法
scala> implicit def person2Engineer(p : Person): Engineer ={
| new Engineer(p.name , 10000)
| }
person2Engineer: (p: Person)Engineer
//定义隐式转换函数,再写toCode方法
scala> def toCode(p: Person){
| p.code
| }
toCode: (p: Person)Unit
//此时不会报错
scala> toCode(new Person("Scala"))
//new person 返回的是Engineer。
Coding ....
//自动使用隐式转换函数,在找code方法时,无此方法,将Person对象隐式转换成Engineer对象,然后就可以调用code方法。此过程是在运行时调用的,自动的进行。
toCode要调用person实例的code方法,而person本身没有code方法
要想代码工作,必须在程序可见范围内隐式转换函数,此时Scala会自动使用隐式转换函数,将传进来的person实例对象通过person2Engineer这个隐式转换函数(可能存在多个隐式转换函数,主要是通过输入类型来判断)进行类型匹配,获得了Engineer对象。此时new Person 返回的是Engineer,然后调用它的方法code。使用完这个功能后,这个功能也消失了。
person和Engineer是并没有关系的。
优点:隐式功能不对外开放,既可以随意增加它或删除它;可以很好地扩展接口。
实例:
import scala.io.Source
import java.io.File
class RicherFile(val file:File){
def read = Source.fromFile(file.getPath()).mkString
}
class File_Implicits( path: String) extends File(path)
object File_Implicits{
implicit def file2RicherFile(file:File)= new RicherFile(file)
//File -> RicherFile
}
object Implicits_Internals {
def main(args: Array[String]) {
val file = new File_Implicits("content.txt")
println(file.read)
}
}
这里我们定义了一个File_Implicits类,注意它其中没有read方法,在他的伴生对象中我们定义了一是转换函数file2RicherFile,然后我们又定义了一个RicherFile类,这个类中包含了read方法。在主函数中我们首先创建了一个File_Implicits对象file,然后打印它的read方法的返回值。首先,scala会在类File_Implicits中找方法read,没有找到,此时不报错,而是在类的伴生对象中找隐式转换,发现隐式转换函数,并且输入类型一样,然后将输入对象自动隐式转换为RicherFile,然后发现RicherFile类中有read方法,scala就直接调用已经隐式转换后的file的read方法,打印结果。
1.2 隐式参数
由运行时上下文实际赋值注入的参数。不需要手动赋值,它会自动发生效果。
实际机制:如果定义了一个隐式参数,在实际运行时我们不需要手动提供这个参数,运行时环境会根据上下文的隐式值(可以静态的写入也可以是动态产生也可以是配置文件等等),会根据隐式值的类型和隐式值本身注入程序中,让我们的程序正常运行。(会在两个范围内查找隐式值,当前作用域的implicit val,implicit var,另外是会到隐式参数类型的伴生对象中去找隐式值,一般都是第二个范围)
scala> class Level(val level: Int)
defined class Level
scala> def toWorker(name: String)(implicit level: Level){ println(name +":"+l.level)
}
toWorker: (name: String)(implicit level: Level)Unit
scala> implicit val level = new Level(8)
//隐式值
level: Level = Level@741b173d
scala> toWorker("Spark")
//上面定义的隐式值在执行过程中系统自动调用,同样也可以传入隐式参数
Spark:8
这里,我们定义了一个Level类,表示等级,有定义了一个科里化函数toWork,他有两个参数,第一个是String的name,第二个是隐式参数Level类型的level,然后函数内部打印name和level的等级。
然后我们定义了一个隐式参数level,然后我们调用toWork方法,但是我们只传入了一个参数,scala打印出了结果,这里,scala从上下文中找到了隐式值level = new Level(6),自动注入到了函数toWork中,作为第二个参数。
3.隐式对象
用implicit修饰的object就是隐式对象
实例:
abstract class Template[T]{
def add(x:T,y:T):T
}
abstract class SubTemplate[T] extends Template[T]{
def unit:T
}
object Implicits_Object{
def main(args:Array[String]){
implicit object StringAdd extends SubTemplate[String]{
def add(x:String,y:String):String=x concat y
def unit:String=""
}
implicit object IntAdd extends SubTemplate[Int]{
def add(x:Int,y:Int):Int=x+y
def unit:Int=0
}
def sum[T] (xs:List[T])(implicit m:SubTemplate[T]):T=
if(xs.isEmpty) m.unit
else m.add(xs.head,sum(xs.tail))
println(sum(List(1,2,3,4,5)))
println(sum(List("Spark","Scala","Kafka")))
}
}
//结果:15, SparkScalaKafka
首先我们定义了几个含有泛型的抽象类,Template是父类,而SubTempalte是子类,而这个子类有有两个子对象StringAdd和IntAdd,下面来看下主函数中定义的sum函数,它是一个含有泛型的函数,它的第一个参数是含有泛型的List类型的xs,第二个参数是含有泛型的SubTemplate类型的隐式参数m,函数返回值是一个泛型,首先,函数里先判断传入的第一个参数是否为空,若为空则调用隐式参数m,有由于scala可以自动进行类型推到,所以运行时,泛型T是一个确定类型,要么为Int要么为String,但是为空时,我们调用隐式参数m的unit是不同的,Int为0,而String为“”,所以我们定义了两个隐式对象对其进行处理,IntAdd隐式类复写了unit,使之为0,StringAdd隐式类复写了unit,使之为””,这样程序就可以正常执行了。同理,隐式对象方法对add方法进行复写,完成了sum操作。
4.隐式类
在四个路径中寻找隐式转换:按优先级查找
a:当前类的伴生对象中找,当前类可能是对象的类或类本身的类
b:把隐式对象和隐式值放在一个Object中,然后导入进来,会在这个里面进行查找
c:在当前作用域中的一些隐式内容中去找
d:用到时才导入或写隐式的import导入
import java.io.File
import scala.io.Source
object Context_Helper{
implicit class FileEnhancer(file:File){
def read=Source.fromFile(file.getPath).mkString
}
implicit class Op(x:Int){
def addSAP(second:Int)=x+second
}
}
object Implicits_Class{
def main(args:Array[String]){
import Context_Helper._
println(1.addSAP(2))
println(new File("1.txt").read)
}
}
//结果:3
这里我们在Context_Helper对象中定义了两个隐式类FileEnhancer和Op,在主方法中,我们到如了这对象中的所有内容,然后println打印时,调用1.addSAP(2):1没有addSAP方法;向RichInt中去找addSAP方法,没有该方法(所以scala会自动隐式转换为RichInt,发现仍然找不到这个方法);再上下文中寻找,寻找到import导入,将FileEnhancer和Op传入,其中Op的参数是整数Int,1为整数,所以他在Op内部进行查找,发现Op中有addSAP方法且参数一致,直接调用addSAP方法,得到结果3,第二个打印函数也类似。
(隐式类构造时,与类名无关,而与类的签名有关,即(x:Int)有关,编译器自动调用)
BTW:
1.命名函数的参数可以声明为implicit,但implicit必须出现在首位,并且是对所有的参数有效,不能只给某些参数声明为implicit,比如:
def maxFunc(implicit i1: Int, i2: Int) = i1 + i2
maxFunc带有两个implicit参数i1和i2。你无法只声明一个implicit参数。你不能这样写
def maxFunc( i1: Int, implicit i2: Int) = i1 + i2
def maxFunc(implicit i1: Int, implicit i2: Int) = i1 + i2
如果你只想声明一个implicit,使用curry,如
def maxFunc(implicit i1: Int)(i2: Int) = i1 + i2
2.匿名函数不能声明隐式参数,即不能这样写:
val f = (implicit s: String) => s+1
3.如果一个函数带有implicit参数,则无法通过 _ 得到该函数引用。你尝试这样做是无法编译的:
def maxFunc(implicit i1: Int, i2: Int) = i1 + i2
val f = maxFunc _ // 编译错误
4.可以给匿名函数的参数加上implicit,比如:
def h( implicit s: String) = println("here : "+s)
def g(func: String => Int) = {
println(func("a"))
}
g{
implicit s => h; 2
//这里的implicit s => h; 2相当于 s => implicit val xx = s; h; 2
}
如果匿名函数有两个参数,貌似不能给参数加上implicit。
*二.并发编程*
一般,所有的高手在进行程序开发时,一定都是并发编程的消息通信模式加上缓存的绝妙的使用。
并发编程是高效利用cpu的核心。Scala中利用Actor实现的。(java的多线程是基于共享全局变量的加锁机制,他一定不可避免的出现死锁和状态失控,而在做分布式时有一个黄金准则就是一定不要使用共享的全局变量,更不要说加锁机制了。)而scala中的Actor去掉了共享的全局变量,变量是私有的;Actor之间的通信是通过发消息是实现的。避免了传统的并发编程中出现的死锁等一列问题,scala中提供一个基于Actor的框架,Akka。
1.创建一个Actor:
>import scala.actors.Actor
>class HiActor extends Actor{
def act(){//类似于java的Thread的run
while(true){//死循环接收消息
receive{
case name:String => println("Hi: "+name)
}
}
}
}
>val actor =new HiActor
>actor.start()//启动
>actor!"Spark"//Actor发消息,!用于发送消息
这里的Actor类似java中Thread,Actor的act方法类似Thread的run方法。Actor中有一个特殊的语法receive来接收消息(它们是通过消息机制实现多线程通信的),然后我们进行模式匹配,若是String类型的值,就将值传给name,然后打印出来。发送消息的语法格式:
Actor名 ! 消息内容。
2.样例类case class 收发消息
>case class Basic(name :String,age:Int)
>case class Worker(name:String,age:Int,salary:Double)
>class BasicActor extends Actor{
def act(){
while(true){
receive{
case Basic(name,age) => println(name+" : "+age)
case Worker(name,age,salary) => println(name+" : "+age+" : "+salary)
}
}
}
}
>val basic=new BasicActor
>basic.start()
>b!Basic("Scala",13)
>b!Worker("Spark",7,15.0)
3.Actor之间发送消息
一个Actor发消息,另一个Actor接收该消息,Spark中类似的有 Master与Worker之间同步(默认情况下,!发送的消息是异步的)
1.!?:必须等到接收方接收到发送方返回的消息,才结束
同步消息 语法格式: Actor名 !? 消息内容
>val result=b!?Worker("Spark,7)
2.!!:获取异步消息处理之后的消息
接收异步消息 语法格式 val 变量名 = actror名 !! 消息内容 (在未来某个时刻变量或得到actor线程的发送的值)
>val future =a!!M //存放接收方处理接收到的消息之后返回给发送方的消息
>val result=future//从future中获得异步发送的消息
作业:分析DAGScheduler Master Worker RDD源码中隐式转换和Actor并发编程