一 函数式编程
1.1将函数值赋给变量
>>Scala中函数可以独立存在,不必像Java一样,还需要依附于类。而且我们可以直接将函数作为值赋给变量
>>Scala语法规定,将函数赋值给变量时,相当于变量就是函数。必须在函数后面加上空格和下划线
scala>def sayHello(name:String) {println("welcome you," + name)}
sayHello:(name: String)Unit
scala>val hello = sayHello _
hello:String => Unit = <function1>
scala>hello("nicky")
welcomeyou,nicky
1.2匿名函数
>>Scala中,函数也可以不需要命名,此时函数被称为匿名函数
>>可以直接定义函数之后,将函数赋值给某个变量
>>也可以将直接定义的匿名函数传入其他函数之中
>>Scala定义匿名函数的语法规则就是,(参数名: 参数类型) =>函数体
scala>val add = (n1:Long,n2:Long) => {println("n1 + n2 = "+ (n1+n2))}
add:(Long, Long) => Unit = <function2>
scala>add(1000,2000)
n1+ n2 = 3000
scala>val compute = (p1:Long,p2:Long,p3:Long) => println("(p1 + p2) / p3 = "+((p1+p2)/p3))
compute:(Long, Long, Long) => Unit = <function3>
scala>compute(1000,2000,5)
(p1+ p2) / p3 = 600
1.3高阶函数
Scala中,可以直接将某个函数传入其它函数,作为参数,接受其他函数作为参数的函数,被称为高阶函数
语法分析
def高阶函数名(传入的函数名:(传入的函数的参数类型) =>返回类型) {调用传入的函数(传入的参数)}
举个例子:
第一步:定一个匿名函数
valcompute = (p1:Long,p2:Long,p3:Long) => println("(p1 + p2) / p3 ="+((p1+p2)/p3))
第二步 定义高阶函数
defaccess(run:(Long,Long,Long) => Unit,p1:Long,p2:Long,p3:Long) {
run(p1,p2,p3)
}
第三步:调用access函数,并把匿名函数传给他
scala>access(compute,1000,2000,6)
(p1+ p2) / p3 = 500
高阶函数的另外一个功能是将函数作为返回值,意思就是我可以返回一个函数
//compute 函数接收参数desc,返回一个函数
scala>def compute(desc:String) = (p1:Long,p2:Long) => println(desc+" p1 + p2= "+ (p1+p2))
compute:(desc: String)(Long, Long) => Unit
//调用compute,返回函数命名add
scala>val add = compute("display the result: ")
add:(Long, Long) => Unit = <function2>
//调用add函数
scala>add(1000,500)
displaythe result: p1 + p2 = 1500
1.4高阶函数的类型推断
defgreeting(func: (String) => Unit, name: String) { func(name) }
一般情况:
greeting((name:String) => println("Hello, " + name), "郭靖")
>>高阶函数可以自动推断出参数类型,不需要我们显式定义参数类型
scala>greeting((name) => println("Hello, "+name),"Beyonce")
Hello,Beyonce
>>而且对于只有一个参数的函数,还可以省却小括号
scala>greeting(name => println("Hello, "+name),"Beyonce")
Hello,Beyonce
>>如果仅有的一个参数在右侧的函数体内只使用一次,则还可以将接受参数省略,并且将参数用_代替
deftriple(func: (Int) => Int) = { func(3) }
triple(3* _)
1.5Scala常用的高阶函数
>>map: 对传入的每个元素都进行映射,返回一个处理后的元素
Array(1,2, 3, 4, 5).map(2 * _)
>>foreach:对传入的每个元素都进行处理,但是没有返回值
(1to 9).map("*" * _).foreach(println _)
>>filter:对传入的每个元素都进行条件判断,如果对元素返回true,则保留该元素,否则过滤掉该元素
(1 to 20).filter(_ % 2 == 0)
>>reduceLeft:从左侧元素开始,进行reduce操作,即先对元素1和元素2进行处理,然后将结果与元素3处理,再将结果与元素4处理,依次类推,即为reduce;reduce操作必须掌握!spark编程的重点!!!
下面这个操作就相当于1 * 2 *3 * 4 * 5 * 6 * 7 * 8 * 9
(1to 9).reduceLeft( _ * _)
>> sortWith:对元素进行两两相比,进行排序
Array(3,2, 5, 4, 10, 1).sortWith(_ < _)
1.6闭包
闭包是一个函数,返回值依赖于声明在函数外部的一个或多个变量通常,我们可以理解为能够访问函数局部变量的另外一个函数。
我们看一下一下代码:
valx = (p:Int) => p * 10
在看一个代码:
valfactor = 10
valy = (p:Int) => p * factor
函数y变量有两个:p 和 factor,其中 p是函数的形式参数,factor是一个自由变量,定义在函数外面。因此这样定义的函数y就是一个闭包,因为他引用到函数外面定义的变量
defgetGreetingFunc(msg: String) = (name: String) => println(msg + "," + name)
valgreetingFuncHello = getGreetingFunc("hello")
valgreetingFuncHi = getGreetingFunc("hi")
为什么getGreetingFunc调用之后,其内部的局部变量,在其内部的另外一个匿名函数能够获取到,这就是诠释了闭包。
原理:
Scala通过为每个函数创建对象来实现闭包,实际上对于getGreetingFunc函数创建的函数,msg是作为函数对象的变量存在的,因此每个函数才可以拥有不同的msg
1.7SAM转换
在Java中,不支持直接将函数传入一个方法作为参数
1.8Currying 函数
其实就相当于函数的链式调用,语法格式如下:
函数名(参数列表)(参数列表)…
比如我们定义一个高阶函数:
scala>def list(name:String,age:Int) = (bookName:String) => println
("Name:"+name+" Age: "+age+" Book: "+bookName) list: (name:String, age: Int)String => Unit正常情况下,我们会这么调用:
第一步:声明一个变量接收list返回的函数
scala>val leadingcharacter = list("郭靖",20)
leadingcharacter:String => Unit = <function1>
第二步:在调用返回的函数
scala>leadingcharacter("射雕英雄传")
Name:郭靖 Age:20 Book: 射雕英雄传
先在我们可以链式调用:
scala>list("黄蓉",18)("射雕英雄传")
Name:黄蓉 Age:18 Book: 射雕英雄传
再举个例子:
scala>def list(name:String)(age:Int)(bookName:String)(author:String) = println("作者-->"+author+"写了小说-->"+bookName+",主人公-->"+name+",年龄-->"+age)
list:(name: String)(age: Int)(bookName: String)(author: String)Unit
scala>list("黄蓉")(18)("射雕英雄传")("金庸")
作者-->金庸 写了小说-->射雕英雄传 ,主人公-->黄蓉, 年龄-->18
1.9偏函数 PartialFunction
偏函数实战
偏函数:一种函数的高级形式,就是没有明确定义输出参数的函数,函数体就是一连串的case语句
偏函数是PartialFucntion[A,B]类的一个实例,这个类有2个方法
一个是apply方法,直接调用可以通过函数体内的case进行匹配,返回结果;另外一函数是isDefinedAt方法,可以返回一个输入,是否跟任何一个case语句匹配
defpartial:PartialFunction[String,String] = {
case"x"=>"nicky";case"y"=>"mondy";case "z"=>"kettle"
}
scala>partial("x")
res8:String = nicky
scala>partial("y")
res9:String = mondy
scala>partial.isDefinedAt("x")
res10:Boolean = true
二 函数式编程之集合操作
2.1Scala的集合体系结构
Scala的集合体系结构主要包括:Iterable,Seq,List,Set,其中Iterable
是所有集合trait的roottrait,类似于java集合框架中的Collection地位
Scala集合分为可变集合和不可变集合:
可变集合就是说,集合的元素可以动态修改,而不可变集合的元素在初始化之后,就无法修改了。分别对应scala.collection.mutable和
Scala.collection.immutable
Seq下包含了Range、ArrayBuffer、List、等子trait。其中Range就代表了一个序列,通常可以使用"1to 10" 这样的语法来产生一个range.
ArrayBuffer就类似于java的ArrayList
2.2List
>>List是一个不可变的集合列表
>>val elements = List(1,2,3,4)
>>List有head和tail,head代表List的第一个元素,tail代表第一个元素之后的所有元素: list.head,list.tail
scala>elements.head
res39:Int = 1
scala>elements.tail
res40:List[Int] = List(2, 3, 4)
>> List有特殊的::操作符,可以用于将head和tail合并成一个List,0 ::list 比如这里:0就是新集合的head,list是一个List,作为新集合的tail
例子:
scala>10::List(9,8,7,6,5,4,3,2,1)
res46:List[Int] = List(10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
>>如果一个List只有一个元素,那么它的head就是这个元素,它的tail是Nil
例子:
defdecorator(l: List[Int], prefix: String) {
if (l != Nil) {
println(prefix + l.head)
decorator(l.tail, prefix)
}
}
2.3LinkedList
>>LinkedList是一个可变的集合列表
>> val l =scala.collection.mutable.LinkedList(1, 2, 3, 4, 5);
>>使用elem可以引用其头部,next引用其尾部,类似于List的head和tail
scala>l.elem
res50:Int = 1
scala>l.next
res51:scala.collection.mutable.LinkedList[Int] = LinkedList(2, 3, 4, 5)
例子:
vallist = scala.collection.mutable.LinkedList(1, 2, 3, 4, 5)
varcurrentList = list
while(currentList != Nil) {
currentList.elem = currentList.elem * 2
currentList = currentList.next
}
2.4Set
>>Set代表一个没有重复元素的集合,和之前的List和LinkedList比较,之前的都可以装重复的元素
>>你就算加入重复元素,也是没有用的:
scala>val s = Set(1,2,3)
s:scala.collection.mutable.Set[Int] = Set(1, 2, 3)
scala>s+=(2,3,4)
res55:s.type = Set(1, 2, 3, 4)
>>Set不保证插入的顺序,和java类似
>> LinkedHashSet会用一个链表维护插入顺序:
vals = new scala.collection.mutable.LinkedHashSet[Int](); i += 1; s += 2; s += 5
>> SortedSet会自动根据key来进行排序:
vals = scala.collection.mutable.SortedSet("orange", "apple","banana")
2.5集合的函数式编程
//map案例实战:为List中每个元素都添加一个前缀
List("Leo","Jen", "Peter", "Jack").map("name is "+ _)
//faltMap案例实战:将List中的多行句子拆分成单词
List("HelloWorld", "You Me").flatMap(_.split(" "))
//foreach案例实战:打印List中的每个单词
List("I","have", "a", "beautiful","house").foreach(println(_))
//zip案例实战:对学生姓名和学生成绩进行关联
List("Leo","Jen", "Peter", "Jack").zip(List(100, 90, 75,83))
三 模式匹配match case
模式匹配,其实类似于Java中的switch case语法,即对一个值进行条件判断,然后针对不同的条件,进行不同的处理。
但是Scala的模式匹配的功能比Java的swichcase语法的功能要强大地多,Java的swichcase语法只能对值进行匹配。但是Scala的模式匹配除了可以对值进行匹配之外,还可以对类型进行匹配、对Array和List的元素情况进行匹配、对caseclass进行匹配、甚至对有值或没值(Option)进行匹配。
Scala是没有Java中的switchcase语法的,相对应的,Scala提供了更加强大的matchcase语法,即模式匹配,类替代switchcase,matchcase也被称为模式匹配
3.1模式匹配的基础语法
>> Scala的matchcase可以匹配各种情况,比如变量的类型、集合的元素、有值或无值
>> matchcase的语法如下:变量 match { case 值 => 代码 }。如果值为下划线,则代表了不满足以上所有情况下的默认情况如何处理
>> matchcase中,只要一个case分支满足并处理了,就不会继续判断下一个case分支了;但是java不一样,只要不break或者return ,则一直会继续判断下去
例子:
def check(grade:String) { grade match { case "A" => {println("Excellent")} case "B" => {println("Good")} case "C" => {println("Just so so")} case _ => {println("you need work harder")} } } |
>>可以在case后的条件判断中,不仅仅只是提供一个值,而是可以在值后面再加一个if守卫,进行双重过滤
def check(name:String, grade:String) { grade match{ case "A" if(name.equals("nicky")) => println(name+", Very Good!!!") case "A" if(name.equals("kity")) => println(name + "are very Excellent") case "B" => println(name + ", you are good") case "C" => println(name + ", you are just so so") case _ => println("you need to work harder") } } |
>>在模式匹配中进行变量赋值
在case默认值情况下,我们可以将_ 改成 _变量名,这时候要匹配的值就可以赋给这个变量,然后在后面的处理语句还可以用到
def check(name:String, grade:String) { grade match{ case "A" if(name.equals("nicky")) => println(name+", Very Good!!!") case "A" if(name.equals("kity")) => println(name + "are very Excellent") case "B" => println(name + ", you are good") case "C" => println(name + ", you are just so so") case _grade if name == "郭靖" => println(name+", you garde is "+ _grade+", come on") case _grade => println(name+",you grade is" +_grade+", so you need to work harder") } } |
3.2对类型进行模式匹配
语法:case 变量:类型 =>代码 和前面普通的匹配值的语法稍有差异
def processException(e: Exception) { e match { case e1: IllegalArgumentException => println("you have illegal arguments! exception is: " + e1) case e2: FileNotFoundException => println("cannot find the file you need read or write!, exception is: " + e2) case e3: IOException => println("you got an error while you were doing IO operation! exception is: " + e3) case _: Exception => println("cannot know which exception you have!" ) } }
object Test { def main(args: Array[String]): Unit = { val t = new Test t.processException(new FileNotFoundException("文件找不到")) t.processException(new IllegalArgumentException("参数异常")) } } |
3.3对Array和List的元素进行模式匹配
>>对Array进行模式匹配,分别可以匹配带有指定元素的数组、带有指定个数元素的数组、以某元素打头的数组
def greeting(arr: Array[String]) { arr match { //数组是否包含指定元素 case Array("郭靖") => println("Hi, 郭靖!") //数组是否包含指定元素个数 case Array(girl1, girl2, girl3) => println("Hi, girls, nice to meet you. " + girl1 + " and " + girl2 + " and " + girl3) //数组是否包含指定元素开始,_*表示后面数量不定,可以有很多 case Array("郭靖", _*) => println("Hi, 郭靖, please introduce your friends to me.") case _ => println("hey, who are you?") } } |
>>对List进行模式匹配,与Array类似,但是需要使用List特有的::操作符
def greeting(list: List[String]) { list match { case "Leo" :: Nil => println("Hi, Leo!") case girl1 :: girl2 :: girl3 :: Nil => println("Hi, girls, nice to meet you. " + girl1 + " and " + girl2 + " and " + girl3) case "Leo" :: tail => println("Hi, Leo, please introduce your friends to me.") case _ => println("hey, who are you?") } } |
3.4case class与模式匹配
>>Scala中提供了一种特殊的类,用caseclass进行声明,中文也可以称作样例类。caseclass其实有点类似于Java中的JavaBean的概念。即只定义field,并且由Scala编译时自动提供getter和setter方法,但是没有method。
>>caseclass的主构造函数接收的参数通常不需要使用var或val修饰,Scala自动就会使用val修饰(但是如果你自己使用var修饰,那么还是会按照var来)
>>Scala自动为caseclass定义了伴生对象,也就是object,并且定义了apply()方法,该方法接收主构造函数中相同的参数,并返回caseclass对象
class Person case class Teacher(name: String, subject: String) extends Person case class Student(name: String, classroom: String) extends Person def judgeIdentify(p: Person) { p match { case Teacher(name, subject) => println("Teacher, name is " + name + ", subject is " + subject) case Student(name, classroom) => println("Student, name is " + name + ", classroom is " + classroom) case _ => println("Illegal access, please go out of the school!") } } |
3.5Option与模式匹配
>>Scala有一种特殊的类型,叫做Option。Option有两种值,一种是Some,表示有值,一种是None,表示没有值。
>>Option通常会用于模式匹配中,用于判断某个变量是有值还是没有值,这比null来的更加简洁明了
val grades = Map("Leo" -> "A", "Jack" -> "B", "Jen" -> "C") def getGrade(name: String) { val grade = grades.get(name) grade match { case Some(grade) => println("your grade is " + grade) case None => println("Sorry, your grade information is not in the system") } } |
四 类型参数
类型参数是什么?类型参数其实就类似于Java中的泛型
那么Scala的类型参数是什么?其实意思与Java的泛型是一样的,也是定义一种类型参数,比如在集合,在类,在函数中,定义类型参数,然后就可以保证使用到该类型参数的地方,就肯定,也只能是这种类型。从而实现程序更好的健壮性
4.1泛型类
泛型类,顾名思义,其实就是在类的声明中,定义一些泛型类型,然后在类内部,比如field或者method,就可以使用这些泛型类型
使用泛型类,通常是需要对类中的某些成员,比如某些field和method中的参数或变量,进行统一的类型限制,这样可以保证程序更好的健壮性和稳定性。
在使用类的时候,比如创建类的对象,将类型参数替换为实际的类型,即可。
Scala自动推断泛型类型特性:直接给使用了泛型类型的field赋值时,Scala会自动进行类型推断。
Java实现
public class Student<T> {
private T localId;
public Student(T localId) { super(); this.localId = localId; }
public String getSchoolId(T hukouId){ return "S-"+hukouId+"-"+getLocalId(); }
public T getLocalId() { return localId; }
public void setLocalId(T localId) { this.localId = localId; }
public static void main(String[] args) { Student s = new Student<Integer>(1000); String newID = s.getSchoolId(2345); System.out.println(newID); } } |
class Student[T](val localId: T) { def getSchoolId(hukouId: T) = "S-" + hukouId + "-" + localId } val leo = new Student[Int](111) |
4.2泛型函数
泛型函数,与泛型类类似,可以给某个函数在声明时指定泛型类型,然后在函数体内,多个变量或者返回值之间,就可以使用泛型类型进行声明,从而对某个特殊的变量,或者多个变量,进行强制性的类型限制。
与泛型类一样,你可以通过给使用了泛型类型的变量传递值来让Scala自动推断泛型的实际类型,也可以在调用函数时,手动指定泛型类型。
def getCard[T](content: T) = { if(content.isInstanceOf[Int]) "card: 001, " + content else if(content.isInstanceOf[String]) "card: this is your card, " + content else "card: " + content } |
4.3上边界Bounds
在指定泛型类型的时候,有时候我们需要对泛型类型的范围进行界定,而不是可以是任何类型。比如:我么要求某个泛型类型,他就必须是某个类的子类,这样程序中就可以放心调用泛型类型继承父类的方法,我们这时候可以利用上下边界的特性。
Scala的上下边界特性允许泛型类型必须是某个类的子类,或者必须是某个类的父类
[T<: Person] 只要是Person或者Person的子类
class Person(val name: String) { def sayHello = println("Hello, I'm " + name) def makeFriends(p: Person) { sayHello p.sayHello } } class Student(name: String) extends Person(name) class Party[T <: Person](p1: T, p2: T) { def play = p1.makeFriends(p2) } |
4.4下边界Bounds
除了指定泛型类型的上边界,还可以指定下边界,即指定泛型类型必须是某个类的父类
class Father(val name: String) class Child(name: String) extends Father(name) def getIDCard[R >: Child](person: R) { if (person.getClass == classOf[Child]) println("please tell us your parents' names.") else if (person.getClass == classOf[Father]) println("sign your name for your child's id card.") else println("sorry, you are not allowed to get id card.") } |
4.5View Bounds
>>上下边界我们虽然可以限定传入的实例必须是某某子类或者父类型。但是某个类与上下边界Bounds指定的父子类没有任何关系,则默认肯定不能接受
>>比如我又希望这种跟限定的父子类范围没有关系的的实例也能接受,我们就可以使用ViewBounds
>>使用ViewBounds要点:
1) 隐式转换不同类型
2) 类型限定语法:[T<% Person]
举个例子:
class Person(val name: String) { def sayHello = println("Hello, I'm " + name) def makeFriends(p: Person) { sayHello p.sayHello } } class Student(name: String) extends Person(name) class Dog(val name: String) { def sayHello = println("Wang, Wang, I'm " + name) } implicit def dog2person(dog: Object): Person = if(dog.isInstanceOf[Dog]) {val _dog = dog.asInstanceOf[Dog]; new Person(_dog.name) } else Nil class Party[T <% Person](p1: T, p2: T) |
4.6Context Bounds
参考5.8
4.7Manifest Context Bounds
在Scala中,如果要实例化一个泛型数组,需要使用[T:Manifest]
泛型类型,这样才能实例化泛型数组
def convert[T: Manifest] (array: T*) = { val arr = new Array[T](array.length) for(i <- 0 until array.length) arr(i) = array(i) arr }
// Exiting paste mode, now interpreting.
convert: [T](array: T*)(implicit evidence$1: Manifest[T])Array[T]
scala> convert("nicky,"alnem","adam","kirairw") <console>:1: error: ')' expected but string literal found. convert("nicky,"alnem","adam","kirairw") ^ <console>:1: error: unclosed string literal convert("nicky,"alnem","adam","kirairw") |
4.8协变和逆变
在Java中,如果有一个父类Super,子类Child. 某一个类Card<T>
,那实际上Card<Child>并不是Card<Super>子类。
Java实现:
public class Card<T>{ public void enterMeet(Card<Master> card){ System.err.println("welcome you join the meeting!!"); } } public class Master { private String name;
public Master(String name) { super(); this.name = name; } } public class Professional extends Master { private String name;
public Professional(String name) { super(name); } } public class Worker { private String name; public Worker(String name) { super(); this.name = name; } } public class Main { public static void main(String[] args) { Card<Master> c1 = new Card<Master>(); c1.enterMeet(c1); Card<Professional> c2 = new Card<Professional>(); c2.enterMeet(c2);//编译报错,即使是Master子类,也不能被兼容 Card<Worker> c3 = new Card<Worker>(); c3.enterMeet(c3);//编译报错 } } |
>>协变covariance[+T]
class Card[+T](val name: String) { def enterMeet(card:Card[Master]){ println("welcome you join the meeting!!"); } } class Father(val name:String) { def desc = println("我是 "+name) }
class Master(val name:String) {
} class Professional(name:String) extends Master(name){
} class Worker(val name:String){
} object Test { def main(args: Array[String]): Unit = { val c1 = new Card[Master]("Master") c1.enterMeet(c1) val c2 = new Card[Professional]("Master") c2.enterMeet(c2)//此处Master可以兼容子类Professional val c3 = new Card[Worker]("Worker") c3.enterMeet(c3)//Worker和 Master没有继承关系,所以编译报错 } } |
协变[+T]总结: 只有满足指定类型或者指定类型子类才可以被兼容,即使是指定类型的父类也不会被兼容
通俗地讲,只要是你或者你的子类我都可以编译通过
>>逆变contravariance[-T]
class Card[-T] (val name: String) { def enterMeet(card: Card[Professional]) { println("welcome to have this meeting!") } } class Father(val name:String) { def desc = println("我是 "+name) } class Master(name:String) extends Father(name) {
} class Professional(name:String) extends Master(name){
} class Worker(val name:String){
} object Test { def main(args: Array[String]): Unit = { val c1 = new Card[Master]("Master") c1.enterMeet(c1)//此处Professional可以兼容父类Master val c2 = new Card[Professional]("Master") c2.enterMeet(c2) val c3 = new Card[Worker]("Worker") c3.enterMeet(c3)//Worker和 Master没有继承关系,所以编译报错 val c4 = new Card[Father]("Father") c4.enterMeet(c4)//此处Professional可以兼容父类Father } }
|
逆变[-T]总结: 只有满足指定类型或者指定类型父类才可以被兼容,即使是指定类型的子类也不会被兼容
通俗地讲,只要是你或者你的父类我都可以编译通过
4.9Existential Type
在Scala里,有一种特殊的类型参数,就是ExistentialType,存在性类型。
Array[T]forSome { type T }
Array[_]
_就是和T的含义差不多,代表着一种占位符
class Card[-_] (val name: String) { def enterMeet(card: Card[Professional]) { println("welcome to have this meeting!") } } |
五 隐式转换与隐式参数
>>Scala提供了隐式转换和隐式参数的功能,牙可以允许你手动指定将某种类型的对象转换成其他类型的对象
>>核心就是定义隐式转换函数(ImplicitConversion Function),只要在编写的程序内引入,就会被Scala自动使用
>>隐式转换函数通常不是由用户手动调用,而是由Scala进行调用,但是如果要使用隐式转换,则需要对隐式转换函数进行导入。通常建议将隐式转换函数定义成one2one形式
5.1隐式转换
隐式转换函数也被称为隐式视图,把一种类型自动转换成另外一种类型,进而使用另外一种类型中的属性和方法,从而满足表达式的要求.
语法:implicitdef 函数名(参数名:参数类型):返回类型 ={…}
作用:如果在隐式作用域里存在这个定义,它会隐式地把原始类型的值转换为增强之后的类型的值(在需要的时候)
class Person(val name:String, val age:Int) { def desc = println("My name is "+name+", and my age is "+age) def shopping(p:Person) = println("I'm shopping with "+p.name) }
class Dog(val name:String, val age:Int) { def desc = println("wang... My name is "+name+", and my age is "+age) }
implicit def dog2Person(obj:Object):Person = { if (obj.getClass == classOf[Dog]) { val _dog = obj.asInstanceOf[Dog] new Person(_dog.name,_dog.age) } else Nil }
scala> val p1 = new Person("nicky",29) p1: Person = Person@42ea287
scala> val p2 = new Person("baobao",27) p2: Person = Person@28da7d11
scala> val d1 = new Dog("dog",4) d1: Dog = Dog@b1534d3
scala> val p3 = dog2Person(d1) p3: Person = Person@16a9eb2e
scala> p1.desc My name is nicky, and my age is 29
scala> p2.desc My name is baobao, and my age is 27
scala> p3.desc My name is dog, and my age is 4
scala> p3.shopping(p1) I'm shopping with nicky |
5.2使用隐式转换加强现有类型
隐式转换非常强大的一个功能,就是可以在不知不觉中加强现有类型的功能; 可以为某个类定义一个加强版的类,并定义互相之间的隐式转换,从而让源类在使用加强版的方法时,由Scala自动进行隐式转换为加强类,然后再调用该方法。
class Man(val name: String) class Superman(val name: String) { def emitLaser = println("emit a laster!") } implicit def man2superman(man: Man): Superman = new Superman(man.name) val leo = new Man("leo") leo.emitLaser |
其实就是隐式转换成某一个。然后Scala会自动调用隐式转化,然后源类就可以调用加强类的方法
5.3隐式转换函数的作用域与导入
在Scala当中,隐式解析一般分为两个阶段:在隐式转换的作用域查找中,如果当前作用域没有隐式转换,编译器会自动到相应源或目标类型的伴生对象中查找隐式转换。
>>当前程序作用域内的可以用唯一标识符表示的隐式转换函数。
object Test{ implicit def father2Child(f:Father):Child = { new Child(f.name) } def main(args: Array[String]): Unit = {
val f = new Father("郭靖") f.game } } |
>>如果当前程序作用域没有发现隐式转化函数,那么就检查是否定义在源类或者目标类的伴生类中
class Father(val name:String) { def desc = println("我是 "+name) }
object Father{ implicit def father2Child(f:Father):Child = { new Child(f.name) } } class Child(val name: String){ def game = println("我和"+name+"在一起玩游戏") } object Test { def main(args: Array[String]): Unit = { val f = new Father("郭靖") f.game } } |
>>如果以上两种情况都没有,那么就需要我们在使用隐式转换的地方
倒入隐式函数,隐式转换函数一般定义在object中
object Convert { implicit def father2Child(f:Father):Child = { new Child(f.name) } } object Test{ def main(args: Array[String]): Unit = { val f = new Father("郭靖") import com.scala.Convert._ f.game } } |
5.4隐式转换的发生时机
>>表达式的类型与预期的不一样
>>使用某个类型的对象,访问的成员不存在,比如某个方法不存在
>>使用某个类型的对象,调用某个方法,虽然该类型有这个方法,但是给方法传入的参数类型,与方法定义的接收参数的类型不匹配
5.5隐式参数
所谓的隐式参数,指的是在函数或者方法中,定义一个用implicit修饰的参数。
优点:我们使用隐式参数,在调用时,不需要手动显示传入参数,它会去在上下文中查找该隐式变量,然后找到之后,注入给这个函数
Scala会在两个范围内查找隐式变量:一种是当前作用域内可见的val或var定义的隐式变量;一种是隐式参数类型的伴生对象内的隐式值
class SignPen { def write(content: String) = println(content) } implicit val signPen = new SignPen def signForExam(name: String) (implicit signPen: SignPen) { signPen.write(name + " come to exam in time.") } |
这个implicitval signPen = new SignPen也可以定义在SingPen 伴生类之种
5.6隐式类 和 隐式object
隐式类的作用是对类的增强
class Father(val name:String) { def desc = println("我是 "+name) } object Test{ def main(args: Array[String]): Unit = { val f = new Father("黄蓉") println(f.addSufix("喜欢靖哥哥")) }
implicit class AddSuffix(f:Father) { def addSufix(suffix:String): String = f.name+suffix } } |
执行过程分析:
=>f.addSufix("喜欢靖哥哥")不会立即报错,而检查当前作用域有没有 用implicit修饰的,同时检查构造参数有没有将Father作为参数构造器的,并且具有addSuffix方法
=>然后用隐式类执行addSuffix方法
Scala中的隐式对象就是在object对象前面加上implicit修饰符,隐式对象在某种程度上而言可以作为隐式值进行使用.
object Test{ def main(args: Array[String]): Unit = { trait OutputTrait{ def output():String; } implicit object Execute extends OutputTrait{ def output():String = "Output a word" } def call(p1:String)(implicit p2: OutputTrait) = println(p1+" "+p2.output()) call("nicky")(Execute) call("belly") } } |
如果上面把def call(p1:String)(implicit p2:OutputTrait) = println(p1+" "+p2.output()) 改成defcall(p1:String)( p2: OutputTrait) = println(p1+" "+p2.output()) 那我们就必须在调用的时候,显示指定一个Output
Trait
5.7Scala中的隐式绑定可能所处的位置以及如何更好的使用隐式转换
隐式绑定可能所处的位置:
1)所有关联类型的伴生对象,包括包对象:即Scala会在关联类型的伴生对象中寻找隐式值,这种行为是Scala语言的核心.
2)Scala.Predef头文件中:每个编译后的Scala文件的开头都有一句:
implicitimport scala.Predef._
Predef头文件中包含很多有用的转换方法,比如在Scala.Predef头文件中有个Java.lang.Integer=>scala.Int的隐式转换.
3)作用域里的所有导入语句:最后一个隐式转换可能存在的位置是源代码里明确的import导入语句.
Scala当中如何更好的使用隐式转换:
1)通过伴生类与伴生对象机制,编译器将会自动导入隐式转换, 而无需用户手动导入隐式转换。
2)当前程序运行的作用域
5.8Scala中的两种隐式类型约束
Scala支持两种类型的约束操作符,他们其实不是真正意义上的类型约束,而是隐式查找。这两种是ViewBounds 和 ContextBounds.
允许你定义一个隐式参数列表来作为泛型类型的约束。
>>View Bounds
用来要求一个可用的隐式转换函数(隐式视图)来将一种类型自动转换为另外一种类型,定义如下:
deffunc[A <% B](p:A) = 函数体
函数func定义一个约束A <%B 意思是参数p的类型为A,并且在调用的地方必须存在或者能够找到隐式转换A 转化为B
它其实等价于以下函数定义方式
deffunc[A](p:A) (implicit m:A=>B) = 函数体
这个func函数也可以用一个不带类型约束的类型参数来定义,有2个参数:
一个接受A类型的参数,第二个接受一个隐式转换函数
class Person(val name: String) { def sayHello = println("Hello, I'm " + name) def makeFriends(p: Person) { sayHello p.sayHello } } class Student(name: String) extends Person(name) class Dog(val name: String) { def sayHello = println("Wang, Wang, I'm " + name) } implicit def dog2person(dog: Object): Person = if(dog.isInstanceOf[Dog]) {val _dog = dog.asInstanceOf[Dog]; new Person(_dog.name) } else Nil class Party[T <% Person](p1: T, p2: T)
|
valp1 = new Person("郭靖")
vald1 = new Person("阿黄")
valparty = new Party (p1,d1)
过程分析:
它首先检查p1是不是Person的类型,我们这里p1是Person类型然后检查的d1是不是Person类型,它在这里并不会立即报错,它发现d1并不是Person类型,然后在当前作用域查找有没有隐式类型转换函数有的话然后执行,Scala自动执行完之后,发现d1是Person类型所以没问题
>>ContextBounds
上下文界定要求一个可用的隐式值存在,且必须存在。
deffunc[T:S](p:T) = 函数体
表示这个函数参数p的类型是T,但是在调用函数func的时候,必须有一个
隐式值S[T]存在,也可以这样写:
deffunc[T](p:T)(implicit arg:S[T]) = 函数体
例子:
object Test{ def main(args: Array[String]): Unit = { def compare1[T:Ordering](arg1:T,arg2:T):T = { if(Ordering[T].compare(arg1, arg2) > 0) arg1 else arg2 } val max1 = compare1(10, 20) println(max1)
def compare2[T](arg1:T,arg2:T)(implicit m:Ordering[T]):T = { if(m.compare(arg1, arg2) > 0) arg1 else arg2 } val max2 = compare2(10, 20) println(max2) } } |
//定义一个函数compare1,接收参数类型必须是T类型的,但是在调用compare1方法的时候,Ordering[T]这个隐式值必须存在
六 Actor入门
Scala中的Actor类似与Java中的多线程,但是不同的是,Scala的Actor提供的模型与多线程有所不同,Scala的Actor提供的模型与多线程有所不同,从而避免多线程并发时出现资源争用的情况,进而提升多线程编程的性能。此外,ScalaActor的这种模型还可以避免死锁等一系列传统多线程编程的问题。
Spark中使用的分布式多线程框架,是Akka。Akka也实现了类似ScalaActor的模型,其核心概念同样也是Actor。因此只要掌握了ScalaActor,那么在Spark源码研究时,至少即可看明白AkkaActor相关的代码。
6.1Actor的创建、启动和消息收发
>>Scala提供了Actortrait来让我们更方便地进行actor多线程编程,就Actortrait就类似于Java中的Thread和Runnable一样,是基础的多线程基类和接口。
>>我们只要重写Actortrait的act方法,即可实现自己的线程执行体,与Java中重写run方法类似。
>>此外,使用start()方法启动actor;使用!符号,向actor发送消息;actor内部使用receive和模式匹配接收消息
import scala.actors.Actor class HelloActor extends Actor { def act() { while (true) { receive { case name: String => println("Hello, " + name) } } } } val helloActor = new HelloActor helloActor.start() helloActor ! "leo" |
6.2收发case class类型的消息
>>Scala的Actor模型与Java的多线程模型之间,很大的一个区别就是,ScalaActor天然支持线程之间的精准通信;即一个actor可以给其他actor直接发送消息。这个功能是非常强大和方便的。
>>要给一个actor发送消息,需要使用“actor !消息”的语法。在scala中,通常建议使用样例类,即caseclass来作为消息进行发送。然后在actor接收消息之后,可以使用scala强大的模式匹配功能来进行不同消息的处理。
case class Login(username: String, password: String) case class Register(username: String, password: String) class UserManageActor extends Actor { def act() { while (true) { receive { case Login(username, password) => println("login, username is " + username + ", password is " + password) case Register(username, password) => println("register, username is " + username + ", password is " + password) } } } } val userManageActor = new UserManageActor userManageActor.start() userManageActor ! Register("leo", "1234"); userManageActor ! Login("leo", "1234") |
6.3Actor之间互相收发消息
>>如果两个Actor之间要互相收发消息,那么scala的建议是,一个actor向另外一个actor发送消息时,同时带上自己的引用;其他actor收到自己的消息时,直接通过发送消息的actor的引用,即可以给它回复消息。
case class Message(content: String, sender: Actor) class LeoTelephoneActor extends Actor { def act() { while (true) { receive { case Message(content, sender) => { println("leo telephone: " + content); sender ! "I'm leo, please call me after 10 minutes." } } } } } class JackTelephoneActor(val leoTelephoneActor: Actor) extends Actor { def act() { leoTelephoneActor ! Message("Hello, Leo, I'm Jack.", this) receive { case response: String => println("jack telephone: " + response) } } } |
6.4同步消息和Future
>>默认情况下,消息都是异步的;但是如果希望发送的消息是同步的,即对方接受后,一定要给自己返回结果,那么可以使用!?的方式发送消息。即valreply = actor !? message。
>>如果要异步发送一个消息,但是在后续要获得消息的返回值,那么可以使用Future。即!!语法。valfuture = actor !! message。val reply = future()。
6.5Actor用法
Scala会建立一个线程池共所有Actor来使用。receive模型是Actor从池中取一个线程一直使用;react模型是Actor从池中取一个线程用完给其他Actor用
实现方式1:
import scala.actors._
object Actor1 extends Actor {// 或者class
// 实现线程
def act(){ react { case _ =>println("ok"); exit} }
}
//发送消息:
Actor1.start! 1001 // 必须调用start
实现方式2:
import scala.actors.Actor._
val a2 = actor { react{ case _ =>println("ok") } } // 马上启动
//发送消息:
a2! "message" // 不必调用start
提示:
! | 发送异步消息,没有返回值。 |
!? | 发送同步消息,等待返回值。(会阻塞发送消息语句所在的线程) |
!! | 发送异步消息,返回值是 Future[Any]。 |
? | 不带参数。查看 mailbox 中的下一条消息。 |
方式1:接受receive
特点:要反复处理消息,receive外层用while(..)
importactors.Actor, actors.Actor._
vala1 = Actor.actor {
varwork = true
while(work){
receive{ // 接受消息, 或者用receiveWith(1000)
casemsg:String => println("a1: " + msg)
casex:Int => work = false; println("a1 stop: " + x)
}
}
}
a1! "hello" // "a1: hello"
a1! "world" // "a1: world"
a1! -1 // "a1 stop: -1"
a1! "no response :("
方式2:接受react,loop
特点:
l 从不返回
l 要反复执行消息处理,react外层用loop,不能用while(..);
l 通过复用线程,比receive更高效,应尽可能使用react
importactors.Actor, Actor._
vala1 = Actor.actor {
react{
case x:Int => println("a1 stop:" + x)
case msg:String => println("a1:" + msg); act()
}
}
a1! "hello" // "a1: hello"
a1! "world" // "a1: world"
a1! -1 // "a1 stop: -1"
a1! "no response :("
如果不用退出的线程,可使用loop改写如下:
vala1 = Actor.actor {
loop {
react{
casex:Int => println("a1 stop: " + x); exit()
case msg:String => println("a1:" + msg)
}
}
}
actor最佳实践
不阻塞actor
actor不应由于处理某条消息而阻塞,可以调用helper-actor处理耗时操作(helperactor虽然是阻塞的,但由于不接受消息所以没问题),以便actor接着处理下一条消息
importactors._, actors.Actor._
val time = 1000
// (1)原来阻塞的程序
val mainActor1 = actor {
loop { react {
case n: Int => Thread.sleep(time)
println(n)
case s => println(s) } }
}
1 to 5 foreach { mainActor1 ! _ } // 5秒钟后打印完数字
// (2)改写由helperactor去阻塞的程序
val mainActor2: Actor = actor {
loop { react {
case n: Int => actor {Thread.sleep(time); mainActor2 ! "wakeup" }
println(n)
case s => println(s) } }
}
1 to 5 foreach { mainActor2 ! _ } // 马上打印数字; 1秒钟后打印5个wakeup
actor之间用且仅用消息来通讯
actor模型让我们写多线程程序时只用关注各个独立的单线程程序(actor),他们之间通过消息来通讯。例如,如果BadActor中有一个GoodActor的引用:
classBadActor(a:GoodActor) extends Actor {...}
那在BadActor中即可以通过该引用来直接调用GoodActor的方法,也可以通过“!”来传递消息。选择后者!因为一旦BadActor通过引用读取GoodActor实例的私有数据,而这些数据可能正被其他线程改写值,结果就避免不了“共享数据-锁”模型中的麻烦事:即必须保证BadActor线程读取GoodActor的私有数据时,GoodActor线程在这块成为“共享数据”的操作上加锁。GoodActor只要有了共享数据,就必须来加锁防范竞用冲突和死锁,你又得从actor模型退回到“共享数据-锁”模型(注:actor对消息是顺序处理的,本来不用考虑共享数据)。
采用不可变消息
cala的actor模型让每个actor的act方法内部接近于单线程环境,你不用当心act方法里面的操作是否线程安全。在act方法中你可以尽情使用非同步、可变对象,因为每个act方法被有效限制在单个线程中,这也是actor模型被称为“share-nothing” 模型(零共享模型)的原因,其数据的作用范围被限制在单个线程中。不过一旦对象内的数据被用于多个actor之间进行消息传递。这时你就必须考虑消息对象是否线程安全。
保证消息对象线程安全的最好方法就是保证只使用不可变对象作为消息对象。消息类中只定义val字段,且只能指向不可变对象。定义这种不可变消息类的简单方法就是使用caseclass, 并保证其所有的val字段都是不可变的。ScalaAPI中提供了很多不可变对象可用,例如基本类型、String、Tuple、List,不可变Set、不可变Map等。
如果你发现确实需要把一个可变对象obj1发送给其他actor,也因该是发送一份拷贝对象obj1.clone过去,而不是把obj1直接发过去。例如,数据对象Array是可变且未做同步的,所以Array只应该由一个actor同时存取,如果需要发送数组arr,就发送arr.clone(arr中的元素也应该是不可变对象),或者直接发送一个不可变对象arr.toList更好。
总结:大部分时候使用不可变对象很方便,不可变对象是并行系统的曙光,它们是易使用、低风险的线程安全对象。当你将来要设计一个和并行相关的程序时,无论是否使用actor,都应该尽量使用不可变的数据结构。
让消息自说明
对每一种消息创建一个对应的caseclass,而不是使用上面的tuple数据结构。虽然这种包装在很多情况下并非必须,但该做法能使actor程序易于理解,例如:
//不易理解,因为传递的是个一般的字符串,很难指出那个actor来响应这个消息
lookerUpper! ("www.scala-lang.org", self)
//改为如下,则指出只有react能处理LoopupIP的actor来处理:
caseclass LookupIP(hostname: String, requester: Actor)
lookerUpper! LookupIP("www.scala-lang.org", self)
不同jvm间的消息访问
服务器端:
objectActorServer extends Application {
import actors.Actor, actors.Actor._,actors.remote.RemoteActor
Actor.actor { // 创建并启动一个 actor
// 当前 actor 监听的端口: 3000
RemoteActor.alive(3000)
// 在 3000 端口注册本 actor,取名为server1。
// 一个参数为 actor 的标识,它以单引号开头,是 Scala 中的 Symbol量,
// Symbol 量和字符串相似,但 Symbol 相等是基于字符串比较的。
// self 指代当前 actor (注意此处不能用 this)
RemoteActor.register('server1,Actor.self)
// 收到消息后的响应
loop {
Actor.react {case msg =>
println("server1 get: " +msg)
}
}
}
}
客户端:
objectActorClient extends Application {
import actors.Actor, actors.remote.Node,actors.remote.RemoteActor
Actor.actor {
// 取得一个节点(ip:port 唯一标识一个节点)
// Node 是个 case class,所以不需要 new
val node = Node("127.0.0.1",3000)
// 取得节点对应的 actor 代理对象
val remoteActor =RemoteActor.select(node, 'server1)
// 现在 remoteActor 就和普通的 actor 一样,可以向它发送消息了!
println("-- begin to sendmessage")
remoteActor ! "ActorClient的消息"
println("-- end")
}
}