匿名内部类
// 在Scala中,匿名子类是非常常见,而且非常强大的。Spark的源码中也大量使用了这种匿名子类。
// 匿名子类,也就是说,可以定义一个类的没有名称的子类,并直接创建其对象,然后将对象的引用赋予一个变量。之后甚至可以将该匿名子类的对象传递给其他函数使用。
class Person(protected val name: String) {
def sayHello = "Hello, I'm " + name
}
val p = new Person("leo") {
override def sayHello = "Hi, I'm " + name
}
def greeting(p: Person { def sayHello: String }) {
println(p.sayHello)
}
抽象类
// 如果在父类中,有某些方法无法立即实现,而需要依赖不同的子来来覆盖,重写实现自己不同的方法实现。此时可以将父类中的这些方法不给出具体的实现,只有方法签名,这种方法就是抽象方法。
// 而一个类中如果有一个抽象方法,那么类就必须用abstract来声明为抽象类,此时抽象类是不可以实例化的
// 在子类中覆盖抽象类的抽象方法时,不需要使用override关键字(也可带上),但如果是重写父类具体方法或成员,则不能省略override
abstract只能修饰类,不能修饰成员与方法,哪怕成员(没有初始化)与方法(没有方法体)是抽象的
abstract class Person(val name: String) {
def sayHello: Unit
}
class Student(name: String) extends Person(name) {
def sayHello: Unit = println("Hello, " + name)
}
抽象field
// 如果在父类中,定义了field,但是没有给出初始值,则此field为抽象field
// 抽象field意味着,scala会根据自己的规则,为var或val类型的field生成对应的getter和setter方法,但是父类中是没有该field的
// 子类必须覆盖field,以定义自己的具体field,并且覆盖抽象field,不需要使用override关键字
abstract class Person {
val name: String
}
class Student extends Person {
val name: String = "leo"
}
没有初始化的成员所在的类要是抽象类:
abstract class A{
var a:String
}
/*class B extends A*/编译时报错:需要重写父类的抽象成员
class B extends A{
/*override*/ var a:String = "a" //也可以省略override
}
除了通过上面直接覆盖父类的抽象成员外,还可以简接通过实现抽象成员所对应的getter与setter方法即可:
class B extends A{
/*override*/ def a = "a" //由于是实现,所以可以省略override
override def a_=(x:String){println(a)}
}
上面是通过实现父类抽象成员所对应的getter与setter方法来重写抽象成员,所以可以看出:没有被初始化的成员所对应的getter与setter方法实质上就是抽象的,所以类要定义是abstract,成员字段本身没有什么抽象不抽象的概念
将trait作为接口使用
// Scala中的Triat是一种特殊的概念
// 首先我们可以将Trait作为接口来使用,此时的Triat就与Java中的接口非常类似
// 在triat中可以定义抽象方法,就与抽象类中的抽象方法一样,只要不给出方法的具体实现即可
// 类可以使用extends关键字继承trait,注意,这里不是implement,而是extends,在scala中没有implement的概念,无论继承类还是trait,统一都是extends
// 类继承trait后,必须实现其中的抽象方法(如果是trait继承trait则不需要,这好比Java中的接口继承接口一样),实现时不需要使用override关键字
// scala不支持对类进行多继承,但是支持多重继承trait,使用with关键字即可
trait HelloTrait {
def sayHello(name: String)
}
trait MakeFriendsTrait {
def makeFriends(p: Person)
}
class Person(val name: String) extends HelloTrait with MakeFriendsTrait with Cloneable {
def sayHello(name: String) = println("Hello, " + name)
def makeFriends(p: Person) = {sayHello(name);println("Hello, my name is " + name + ", your name is " + p.name)}
}
val p1 = new Person("leo")
val p2 = new Person("lily")
p1.makeFriends(p2)
在Trait中定义具体方法
// Scala中的Triat可以不是只定义抽象方法,还可以定义具体方法,此时trait更像是包含了通用工具方法的东西
// 有一个专有的名词来形容这种情况,就是说trait的功能混入了类
// 举例来说,trait中可以包含一些很多类都通用的功能方法,比如打印日志等等,spark中就使用了trait来定义了通用的日志打印方法
trait Logger {
def log(message: String) = println(message)
}
class Person(val name: String) extends Logger {
def makeFriends(p: Person) {
println("Hi, I'm " + name + ", I'm glad to make friends with you, " + p.name)
log("makeFriends methdo is invoked with parameter Person[name=" + p.name + "]")
}
}
val p1 = new Person("leo")
val p2 = new Person("lily")
p1.makeFriends(p2)
在Trait中定义具体字段
// Scala中的Triat可以定义具体field,此时继承trait的类就自动获得了trait中定义的field
trait Person {
val eyeNum: Int = 2
}
class Student(val name: String) extends Person {
def sayHello = println("Hi, I'm " + name + ", I have " + eyeNum + " eyes.")
}
val s = new Student("leo")
s.sayHello
在Trait中定义抽象字段
// Scala中的Triat可以定义抽象field,而trait中的具体方法则可以基于抽象field来编写
// 但是继承trait的类,则必须覆盖抽象field,提供具体的值
trait SayHello {
val msg: String //抽象字段
def sayHello(name: String) = println(msg + ", " + name) // 具体方法调用抽象字段(实质上是调用val抽象字段所对应的getter抽象方法),相当于Java中的模式方法
class Person(val name: String) extends SayHello {
val msg: String = "hello"
def makeFriends(p: Person) {
sayHello(p.name)
println("I'm " + name + ", I want to make friends with you!")
}
}
val p1 = new Person("leo")
val p2 = new Person("lily")
p1.makeFriends(p2)
为实例混入trait
// 有时我们可以在创建类的对象时,指定该对象混入某个trait,这样,就只有这个对象混入该trait的方法,而类的其他对象则没有
trait Logged {
def log(msg: String) {}
}
trait MyLogger extends Logged {
override def log(msg: String) { println("log: " + msg) }
}
class Person(val name: String) extends Logged {
def sayHello { println("Hi, I'm " + name); log("sayHello is invoked!") }
}
val p1 = new Person("leo")
p1.sayHello // Hi, I'm leo
val p2 = new Person("jack") with MyLogger //实例化时混入
p2.sayHello // Hi, I'm jack
//log: sayHello is invoked!
trait调用链
// Scala中支持让类继承多个trait后,依次调用多个trait中的同一个方法,只要让多个trait的同一个方法中,在方法最后都执行“super.方法”来调用父类方法即可
// 类中调用多个trait中都有的这个方法时,首先会从最右边的trait的方法开始执行,然后依次往左执行,形成一个调用链条
// 这种特性非常强大,其实就相当于设计模式中的责任链模式的一种具体实现
trait Handler {
def handle(data: String) {}
}
trait DataValidHandler extends Handler {
override def handle(data: String) {
println("check data: " + data)
super.handle(data)
}
}
trait SignatureValidHandler extends Handler {
override def handle(data: String) {
println("check signature: " + data)
super.handle(data)
}
}
class Person(val name: String) extends SignatureValidHandler with DataValidHandler {
def sayHello = { println("Hello, " + name); handle(name) }
}
val p = new Person("leo")
p.sayHello
Hello, leo
check data: leo
check signature: leo
在trait中覆盖抽象方法
// 在trait中,是可以覆盖父trait的抽象方法的
// 但是覆盖时,如果使用了“super.方法”形式调用了父类抽象方法,则无法通过编译。因为super.方法就会去掉用父trait的抽象方法,此时子trait的该方法还是会被认为是抽象的,所以在override的同时还需要加上abstract
// 此时如果要通过编译,就得给子trait的方法加上abstract override修饰
trait Logger {
def log(msg: String)
}
trait MyLogger extends Logger {
abstract override def log(msg: String) { println("MyLogger.log()");super.log(msg) }
}
class BasicLog extends Logger{
def log(msg: String) { println("BasicLog.log()"); println(msg) }
}
class Person(val name: String) extends BasicLog with MyLogger {
def makeFriends(p: Person) {
println("Hi, I'm " + name + ", I'm glad to make friends with you, " + p.name)
log("makeFriends methdo is invoked with parameter Person[name=" + p.name + "]")
}
}
val p1 = new Person("leo")
val p2 = new Person("lily")
p1.makeFriends(p2)
Hi, I'm leo, I'm glad to make friends with you, lily
MyLogger.log()
BasicLog.log()
makeFriends methdo is invoked with parameter Person[name=lily]
混合使用trait的具体方法和抽象方法
// 在trait中,可以混合使用具体方法和抽象方法
// 可以让具体方法依赖于抽象方法,而抽象方法则放到继承trait的类中去实现
// 这种trait其实就是设计模式中的模板设计模式的体现
trait Valid {
def getName: String //抽象方法
def valid: Boolean = { //具体方法中调用抽象方法,相当于Java中的模板方法
getName == "leo"
}
}
class Person(val name: String) extends Valid {
println(valid)
def getName = name
}
val p = new Person("leo") //true
trait的构造机制
// 在Scala中,trait也是有构造代码的,也就是trait中的,不包含在任何方法中的代码
// 而继承了trait的类的构造机制如下:1、父类的构造函数执行;2、trait的构造代码执行,多个trait从左到右依次执行;3、构造trait时会先构造父trait,如果多个trait继承同一个父trait,则父trait只会构造一次;4、所有trait构造完毕之后,子类的构造函数执行
class Person { println("Person's constructor!") }
trait Logger { println("Logger's constructor!") }
trait MyLogger extends Logger { println("MyLogger's constructor!") }
trait TimeLogger extends Logger { println("TimeLogger's constructor!") }
class Student extends Person with MyLogger with TimeLogger {
println("Student's constructor!")
}
val s = new Student
trait field的初始化
// 在Scala中,trait的构造函数是不能接参数的(包括主构造器与辅助构造器),即trait不能定义辅助构造器,这是trait与class的唯一区别,但是如果需求就是要trait能够对field进行初始化,该怎么办呢?只能使用Scala中非常特殊的一种高级特性——提前定义
trait SayHello {
val msg: String
println("1、SayHello")
println(msg.toString) // 抛NullPointerException异常。由于在调用msg成员字段时,发现在msg是被重新实现(或重写,这里为实现),则会去调用子类中的实现的msg成员,但由于此时子类构造器还未执行,所以子类msg还没来得及初始化,所以返回null,最终导致空指针异常
}
class Person extends SayHello{
println("2、Person")
val msg:String = "init"
}
new Person // 抛NullPointerException异常,原因父trait构造代码会先于子类构造器执行,在执行msg.toString时子类中的msg还没有来得及初始化。但如果将上面的val都修改为def,则可以正常运行。因为初始化父类时,由于子类实现(或重写,这里为实现)了msg方法,所以msg.toString会去调用子类实现的msg方法而返回"init",即使此时子类还没有被初始化:
trait SayHello {
def msg: String
println("1、SayHello")
println(msg.toString)
}
class Person extends SayHello{
println("2、Person")
def msg:String = "init"
}
new Person
即使父类提供了初始化,但还是抛NullPointerException,原因是子类重写了父类该字段msg,在执行父类构造器中的msg.toString时,msg使用的是子类中被重写过的,但此时子类构造器还未被执行,所以子类的msg此时还为null:
trait SayHello {
val msg: String = "000"
println("1、SayHello")
println(msg.toString) // NullPointerException
}
class Person extends SayHello{
println("2、Person")
override val msg:String = "init"
}
new Person
而下面的则不会抛异常了,原因是子类没有重写msg字段,所以父类构造器在执行时,msg使用的还是父中的msg,且已经被初始化过了:
trait SayHello {
val msg: String = "000"
println("1、SayHello")
println(msg.toString) // 不会抛异常,注意:此名要放在上面msg初始化语句的后面,否则还是会抛空指针
}
class Person extends SayHello{
println("2、Person")
}
new Person
下面根据前面的知识(字段与方法相互实现与重写),结合上面的经验,分析分析一下下面的情况:
以下也可以,原因也是通过方法的多态来初始化:
trait SayHello {
var msg: String
println("1、SayHello")
println(msg.toString)//会去调用子类实现方法msg,顺利执行
}
class Person extends SayHello{
println("2、Person")
def msg:String = {println("person.msg");"init" }
def msg_=(x:String) = println(x)
}
new Person
trait SayHello {
var msg: String
println("1、SayHello")
println(msg.toString) // 抛 NullPointerException,原因父类中的msg被子类实现过,但父类调用时,子类还未初始msg字段
}
class Person extends SayHello{
println("2、Person")
var msg:String = "init"
}
new Person
上面除了通过调用子类实现(或重写)方法解决问题外,下面还可以通过提前定义方式来初始化:
trait SayHello {
val msg: String
println("3、SayHello")
println(msg.toString)
}
class Person{println("2、Person")}
val p = new {
val msg: String = {println("1、init");"init"} // 实例化时提前初始化
} with Person with SayHello
1、init -> 2、Person -> 3、SayHello
注意上面new … with与class …extends…with的区别,new…with是动态混入,执行构造器是从new后面的类(或块,这里为块)开始从左到右依次执行;而class…extends…with则是静态混入,在定义class时就已确定,其构造器是从extends后面的类开始从左往右依次执行,执行完后最后执行class 后面指定的类的构造器。如下面的new … with形式构造顺序:
trait A{
println("a")
}
class B extends A{
println("b")
}
trait C extends A{
println("c")
}
new B with C // a -> b -> c
class…extends…with构造顺序:
trait A{
println("a")
}
trait B{
println("b")
}
class C extends A with B{
println("c")
}
new C // a -> b -> c
下面是另一种初始化方式(class …extends…with静态定义方式),此种方式比上面初始化方式好理解一点:
trait SayHello {
val msg: String
println("2、SayHello")
println(msg.toString)
}
class Person extends {
val msg: String = {println("1、init");"init"} // 类定义时提前初始化
} with SayHello {
println("3、Person")
}
new Person
// 另外一种方式就是使用lazy value
trait SayHello {
lazy val msg: String = {println("SayHello");null} // 此句不会执行
println(msg.toString) // 此句会调用子类重写过的msg成员,由于子类msg定义成了lazy,而lazy变量有个特性就是在使用时会执行右边表达式,所以在这里调用msg.toString方法时,就会触发懒加载右边的计算表达式,所以lazy字段不是由类来初始化的,而是由调用时机来决定,所以子类中的lazy msg会先于子类其他成员被初始化
println("2")
}
class Person extends SayHello {
println("3")
val m: String = {println("4");"m"}
override lazy val msg: String = {println("1");"init"}
}
new Person
1
init
2
3
4
trait继承class
// 在Scala中,trait也可以继承自class,此时这个class就会成为所有继承该trait的类的父类
class MyUtil {
def printMessage(msg: String) = println(msg)
}
trait Logger extends MyUtil {
def log(msg: String) = printMessage("log: " + msg)
}
class Person(val name: String) extends Logger {
def sayHello {
log("Hi, I'm " + name)
printMessage("Hi, I'm " + name)
}
}
new Person("leo").sayHello
log: Hi, I'm leo
Hi, I'm leo
将函数赋值给变量
// Scala中的函数是一等公民,可以独立定义,独立存在,而且可以直接将函数作为值赋值给变量
// Scala的语法规定,将函数赋值给变量时,必须在函数后面加上空格和下划线
def sayHello(name: String) { println("Hello, " + name) }
val sayHelloFunc = sayHello _
sayHelloFunc("leo")
匿名函数
// Scala中,函数也可以不需要命名,此时函数被称为匿名函数。
// 可以直接定义函数之后,将函数赋值给某个变量;也可以将直接定义的匿名函数传入其他函数之中
// Scala定义匿名函数的语法规则就是,(参数名: 参数类型) => 函数体
// 这种匿名函数的语法必须深刻理解和掌握,在spark的中有大量这样的语法,如果没有掌握,是看不懂spark源码的
val sayHelloFunc = (name: String) => println("Hello, " + name)
sayHelloFunc("leo")
变量带返回类型:
val sayHelloFunc:String=>Unit = (name: String) => println("Hello, " + name)
高阶函数
// Scala中,由于函数是一等公民,因此可以直接将某个函数传入其他函数,作为参数。这个功能是极其强大的,也是Java这种面向对象的编程语言所不具备的。
// 接收其他函数作为参数的函数,也被称作高阶函数(higher-order function)
val sayHelloFunc = (name: String) => println("Hello, " + name)
def greeting(func: (String) => Unit, name: String) { func(name) }
greeting(sayHelloFunc, "leo")
Array(1, 2, 3, 4, 5).map((num: Int) => num * num)
// 高阶函数的另外一个功能是将函数作为返回值,即返回值就是一个函数,如下面根据不同的msg生成不同的函数
def getGreetingFunc(msg: String) = (name: String) => println(msg + ", " + name)
var greetingFunc = getGreetingFunc("hello")
greetingFunc("leo")
greetingFunc = getGreetingFunc("hi")
greetingFunc("leo")
高阶函数的类型推断
// 高阶函数可以自动推断出参数类型,而不需要写明类型;而且对于只有一个参数的函数,还可以省去其小括号;
def greeting(func: (String) => Unit, name: String) { func(name) }
greeting((name: String) => println("Hello, " + name), "leo")
greeting((name) => println("Hello, " + name), "leo")
greeting(name => println("Hello, " + name), "leo")
// 只要某个参数只在函数体里出现一次,则可以使用下划线 _ 来替换这个参数
def triple(func: (Int) => Int) = { func(3) }
triple(3 * _)
// 诸如3 * _的这种语法,必须掌握!!spark源码中大量使用了这种语法!
有多少个下划线,则就表示有多少个不同的参数。多个占位符时,第一个下划线表示第一个参数,第二个下划线表示第二个参数,以此类推;所以同一参数多处出现时是无法使用这种占位符来表示的。
使用占位符时,有时无法推导出类型,如:
scala> val f = _ + _
此时需明确写出类型:
scala> val f = (_: Int) + (_: Int)
f: (Int, Int) => Int =
Scala的常用高阶函数
// map: 对传入的每个元素都进行映射,返回一个处理后的元素
Array(1, 2, 3, 4, 5).map(2 * _)
// foreach: 对传入的每个元素都进行处理,但是没有返回值
(1 to 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
(1 to 9).reduceLeft( _ * _)
// sortWith: 对元素进行两两相比,进行排序
Array(3, 2, 5, 4, 10, 1).sortWith(_ < _)
闭包
// 闭包最简洁的解释:函数在变量不处于其有效作用域时,还能够对变量进行访问,即为闭包
def getGreetingFunc(msg: String) = (name: String) => println(msg + ", " + name)
val greetingFuncHello = getGreetingFunc("hello")
val greetingFuncHi = getGreetingFunc("hi")
// 两次调用getGreetingFunc函数,传入不同的msg,并创建不同的函数返回
// 然而,msg只是一个局部变量,却在getGreetingFunc执行完之后,还可以继续存在创建的函数之中;greetingFuncHello("leo"),调用时,值为"hello"的msg被保留在了函数体内部,可以反复的使用
// 这种变量超出了其作用域,还可以使用的情况,即为闭包
// Scala通过为每个函数创建对象来实现闭包,实际上对于getGreetingFunc函数创建的函数,msg是作为函数对象的变量存在的,因此每个函数才可以拥有不同的msg
SAM转换
如果Scala调用Java的某个方法传入的是一个SAM,则可以通过Scala提供的很方便的一种转换,将函数对象会传给Java方法
// 在Java中,由于不支持直接将函数传入一个方法作为参数,通常来说,唯一的办法就是定义一个实现了某个接口的类的实例对象,该对象只有一个方法,犹如这样接口只有单个的抽象方法,就叫single abstract method,简称为SAM
// 由于Scala是可以调用Java的代码的,因此当我们调用Java的某个方法时,可能就不得不创建SAM传递给方法,非常麻烦;但是Scala又是支持直接传递函数的。此时就可以使用Scala提供的,在调用Java方法时,使用Scala提供的SAM转换功能,即将SAM转换为Scala函数
// 要使用SAM转换,需要使用Scala提供的特性,隐式转换
import javax.swing._
import java.awt.event._
val button = new JButton("Click")
button.addActionListener(new ActionListener {// ActionListener接口只有一个抽象方法,这样的接口叫SAM
override def actionPerformed(event: ActionEvent) {
println("Click Me!!!")
}
})
**implicit** def getActionListener(actionProcessFunc: (ActionEvent) => Unit) = new ActionListener {
override def actionPerformed(event: ActionEvent) {
actionProcessFunc(event)
}
}
button.addActionListener((event: ActionEvent) => println("Click Me!!!"))
Currying函数
// Curring函数,指的是,将原来接收两个参数的一个函数,转换为两个函数,第一个函数接收原先的第一个参数,然后返回接收原先第二个参数的第二个函数。
// 在函数调用的过程中,就变为了两个函数连续调用的形式
// 在Spark的源码中,也有体现,所以对()()这种形式的Curring函数,必须掌握!
def sum(a: Int, b: Int) = a + b
sum(1, 1)
def sum2(a: Int) = (b: Int) => a + b
sum2(1)(1)
def sum3(a: Int)(b: Int) = a + b
sum2(1)(1)
return到外层函数
// Scala中,不需要使用return来返回函数的值,函数最后一行语句的值,就是函数的返回值。在Scala中,return用于在匿名函数中返回值给包含匿名函数的带名函数(即外层函数),并作为带名函数的返回值。
// 使用return的匿名函数,是必须给出返回类型的,否则无法通过编译
def greeting(name: String) = {
def sayHello(name: String):**String** = {
return "Hello, " + name
}
sayHello(name)
}
greeting("leo")
Scala的集合体系结构
// Scala中的集合体系主要包括:Iterable、Seq、Set、Map。其中Iterable是所有集合trait的根trait。这个结构与Java的集合体系非常相似(最上层为public interface Collection extends Iterable)。
// Scala中的集合是分成可变和不可变两类集合的,其中可变集合就是说,集合的元素可以动态修改,而不可变集合的元素在初始化之后,就无法修改了。分别对应scala.collection.mutable和scala.collection.immutable两个包。
// Seq下包含了Range、ArrayBuffer、List等子trait。其中Range就代表了一个序列,通常可以使用“1 to 10”这种语法来产生一个Range。 ArrayBuffer就类似于Java中的ArrayList。
List
// List代表一个不可变的列表
// List的创建,val list = List(1, 2, 3, 4)
// List有head和tail,head代表List的第一个元素,tail代表第一个元素之后的所有元素,list.head,list.tail
// List有特殊的::操作符,可以用于将head和tail合并成一个List,0 :: list
// ::这种操作符要清楚,在spark源码中都是有体现的,一定要能够看懂!
// 如果一个List只有一个元素,那么它的head就是这个元素,它的tail是Nil
// 案例:用递归函数来给List中每个元素都加上指定前缀,并打印加上前缀的元素
def decorator(l: List[Int], prefix: String) {
if (l != Nil) {
println(prefix + l.head)
decorator(l.tail, prefix)
}
}
LinkedList
// LinkedList代表一个可变的列表,使用elem可以引用其头部,使用next可以引用其尾部
// val l = scala.collection.mutable.LinkedList(1, 2, 3, 4, 5); l.elem; l.next
// 案例:使用while循环将LinkedList中的每个元素都乘以2
val list = scala.collection.mutable.LinkedList(1, 2, 3, 4, 5)
var currentList = list
while (currentList != Nil) {
currentList.elem = currentList.elem * 2
currentList = currentList.next
}
// 案例:使用while循环将LinkedList中,从第一个元素开始,每隔一个元素,乘以2
val list = scala.collection.mutable.LinkedList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
var currentList = list
var first = true
while (currentList != Nil && currentList.next != Nil) {
if (first) { currentList.elem = currentList.elem * 2; first = false }
currentList = currentList.next.next
if (currentList != Nil) currentList.elem = currentList.elem * 2
}
Set
// Set代表一个没有重复元素的集合,Set为trait,分为可变与不可变两种trait
// 将重复元素加入Set是没有用的,比如val s = Set(1, 2, 3); s + 1; s + 4
// 而且Set是不保证插入顺序的,也就是说,Set中的元素是乱序的,
val s = new scala.collection.mutable.**HashSet**[Int](); s += 1; s += 2; s += 5
// LinkedHashSet会用一个链表维护插入顺序,
val s = new scala.collection.mutable.LinkedHashSet[Int]();
i += 1; s += 2; s += 5
// SrotedSet会自动根据key来进行排序,
val s = scala.collection.mutable.SortedSet("orange", "apple", "banana")
集合的函数式编程
// 集合的函数式编程非常非常非常之重要!!!
// 必须完全掌握和理解Scala的高阶函数是什么意思,Scala的集合类的map、flatMap、reduce、reduceLeft、foreach等这些函数,就是高阶函数,因为可以接收其他函数作为参数
// 高阶函数的使用,也是Scala与Java最大的一点不同!!!因为Java里面是没有函数式编程的,也肯定没有高阶函数,也肯定无法直接将函数传入一个方法,或者让一个方法返回一个函数
// 对Scala高阶函数的理解、掌握和使用,可以大大增强你的技术,而且也是Scala最有诱惑力、最有优势的一个功能!!!
// 此外,在Spark源码中,有大量的函数式编程,以及基于集合的高阶函数的使用!!!所以必须掌握,才能看懂spark源码
// map案例实战:为List中每个元素都添加一个前缀
List("Leo", "Jen", "Peter", "Jack").map("name is " + _)
// faltMap案例实战:将List中的多行句子拆分成单词
List("Hello World", "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))
函数式编程综合案例:统计多个文本内的单词总数
// 使用scala的io包将文本文件内的数据读取出来
val lines01 = scala.io.Source.fromFile("C://Users//Administrator//Desktop//test01.txt").mkString
val lines02 = scala.io.Source.fromFile("C://Users//Administrator//Desktop//test02.txt").mkString
// 使用List的伴生对象,将多个文件内的内容创建为一个List
val lines = List(lines01, lines02)
// 下面这一行才是我们的案例的核心和重点,因为有多个高阶函数的链式调用,以及大量下划线的使用,如果没有透彻掌握之前的课讲解的Scala函数式编程,那么下面这一行代码,完全可能会看不懂!!!
// 但是下面这行代码其实就是Scala编程的精髓所在,就是函数式编程,也是Scala相较于Java等编程语言最大的功能优势所在
// 而且,spark的源码中大量使用了这种复杂的链式调用的函数式编程
// 而且,spark本身提供的开发人员使用的编程api的风格,完全沿用了Scala的函数式编程,比如Spark自身的api中就提供了map、flatMap、reduce、foreach,以及更高级的reduceByKey、groupByKey等高阶函数
// 如果要使用Scala进行spark工程的开发,那么就必须掌握这种复杂的高阶函数的链式调用!!!
lines.flatMap(_.split(" ")).map((_, 1)).map(_._2).reduceLeft(_ + _)
模式匹配
// Scala是没有Java中的switch case语法的,相对应的,Scala提供了更加强大的match case语法,即模式匹配,类替代switch case,match case也被称为模式匹配
// Scala的match case与Java的switch case最大的不同点在于,Java的switch case仅能匹配变量的值,比1、2、3等;而Scala的match case可以匹配各种情况,比如变量的类型、集合的元素、有值或无值
// match case的语法如下:变量**** match { case ****值 => ****代码 }。如果值为下划线,则代表了不满足以上所有情况下的默认情况如何处理。此外,match case中,只要一个case分支满足并处理了,就不会继续判断下一个case分支了。(与Java不同,java的switch case需要用break阻止)
// match case语法最基本的应用,就是对变量的值进行模式匹配
// 案例:成绩评价
def judgeGrade(grade: String) {
grade match {
case "A" => println("Excellent")
case "B" => println("Good")
case "C" => println("Just so so")
case _ => println("you need work harder")
}
}
在模式匹配中使用if守卫
// Scala的模式匹配语法,有一个特点在于,可以在case后的条件判断中,不仅仅只是提供一个值,而是可以在值后面再加一个if守卫,进行双重过滤
// 案例:成绩评价(升级版)
def judgeGrade(**name**: String, grade: String) {
grade match {
case "A" => println(name + ", you are excellent")
case "B" => println(name + ", you are good")
case "C" => println(name + ", you are just so so")
case _ **if** **name** == "leo" => println(name + ", you are a good boy, come on")
case _ => println("you need to work harder")
}
}
在模式匹配中进行变量赋值
// Scala的模式匹配语法,有一个特点在于,可以将模式匹配的默认情况,将下划线替换为一个变量名,此时模式匹配语法就会将要匹配的值赋值给这个变量,从而可以在后面的处理语句中使用要匹配的值
// 为什么有这种语法??思考一下。因为只要使用用case匹配到的值,是不是我们就知道这个只啦!!在这个case的处理语句中,是不是就直接可以使用写程序时就已知的值!
// 但是对于下划线这种情况,所有不满足前面的case的值,都会进入这种默认情况进行处理,此时如果我们在处理语句中需要拿到具体的值进行处理呢?那就需要使用这种在模式匹配中进行变量赋值的语法!!
// 案例:成绩评价(升级版)
def judgeGrade(name: String, grade: String) {
grade match {
case "A" => println(name + ", you are excellent")
case "B" => println(name + ", you are good")
case "C" => println(name + ", you are just so so")
case grade_ if name == "leo" => println(name + ", you are a good boy, come on, your grade is " + grade+ " : " + grade_)
case _ => println("you need to work harder, your grade is " + grade)
}
}
对类型进行模式匹配
// Scala的模式匹配一个强大之处就在于,可以直接匹配类型,而不是值!!!这点是java的switch case绝对做不到的。
// 理论知识:对类型如何进行匹配?其他语法与匹配值其实是一样的,但是匹配类型的话,就是要用“case 变量: 类型 => 代码”这种语法,而不是匹配值的“case 值 => 代码”这种语法。
// 案例:异常处理
import java.io._
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!" )
}
}
processException(new IOException ("File not found"))
对Array和List进行模式匹配
// 对Array进行模式匹配,分别可以匹配带有指定元素的数组、带有指定个数元素的数组、以某元素打头的数组
// 对List进行模式匹配,与Array类似,但是需要使用List特有的::操作符
// 案例:对朋友打招呼
def greeting(arr: Array[String]) {
arr match {
case Array("Leo") => println("Hi, Leo!")
case Array(girl1, girl2, girl3) => println("Hi, girls, nice to meet you. " + girl1 + " and " + girl2 + " and " + girl3)
case Array("Leo", _*) => println("Hi, Leo, please introduce your friends to me.")
case _ => println("hey, who are you?")
}
}
greeting(Array("Leo","Jack"))
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?")
}
}
greeting(List("Leo","Jack"))
case class与模式匹配
// Scala中提供了一种特殊的类,用case class进行声明,中文也可以称作样例类。case class其实有点类似于Java中的JavaBean的概念。即只定义field,并且由Scala编译时自动提供getter和setter方法,但是没有method。
// case class的主构造函数接收的参数通常不需要使用var或val修饰,Scala自动就会使用val修饰(但是如果你自己使用var修饰,那么还是会按照var来)
// Scala自动为case class定义了伴生对象,也就是object,并且定义了apply()方法,该方法接收主构造函数中相同的参数,并返回case class对象
// 案例:学校门禁
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!")
}
}
judgeIdentify(Student("Leo","1"))
Option与模式匹配
// Scala有一种特殊的类型,叫做Option。Option有两种值,一种是Some,表示有值,一种是None,表示没有值。
// Option通常会用于模式匹配中,用于判断某个变量是有值还是没有值,这比null来的更加简洁明了
// Option的用法必须掌握,因为Spark源码中大量地使用了Option,比如Some(a)、None这种语法,因此必须看得懂Option模式匹配,才能够读懂spark源码。
// 案例:成绩查询
val grades = Map("Leo" -> "A", "Jack" -> "B", "Jen" -> "C")
def getGrade(name: String) {
val grade = grades.get(name)
grade match {
case Some(grade1) => println("your grade is " + grade1)
case None => println("Sorry, your grade information is not in the system")
}
}
getGrade("Lily")
getGrade("Leo")
Scala集合类的某些标准操作会产生Option可选值,如Map的get方法,查到值时返回Some(value)对象,没查到时返回None对象(而Java中返回的为Null,这会容易导致程序运行错误)
类型参数
类型参数是什么?类型参数其实就类似于Java中的泛型。先说说Java中的泛型是什么,比如我们有List a = new ArrayList(),接着a.add(1),没问题,a.add("2"),然后我们a.get(1) == 2,对不对?肯定不对了,a.get(1)获取的其实是个String——"2",String——"2"怎么可能与一个Integer类型的2相等呢?
所以Java中提出了泛型的概念,其实也就是类型参数的概念,此时可以用泛型创建List,List a = new ArrayListInteger,那么,此时a.add(1)没问题,而a.add("2")呢?就不行了,因为泛型会限制,只能往集合中添加Integer类型,这样就避免了上述的问题。
那么Scala的类型参数是什么?其实意思与Java的泛型是一样的,也是定义一种类型参数,比如在集合,在类,在函数中,定义类型参数,然后就可以保证使用到该类型参数的地方,就肯定,也只能是这种类型。从而实现程序更好的健壮性。
此外,类型参数是Spark源码中非常常见的,因此同样必须掌握,才能看懂spark源码。
泛型类
// 泛型类(类声明时类名后面中括号中的即为类型参数),顾名思义,其实就是在类的声明中,定义一些泛型类型,然后在类内部,比如field或者method,就可以使用这些泛型类型。
// 使用泛型类,通常是需要对类中的某些成员,比如某些field和method中的参数或变量,进行统一的类型限制,这样可以保证程序更好的健壮性和稳定性。
// 如果不使用泛型进行统一的类型限制,那么在后期程序运行过程中,难免会出现问题,比如传入了不希望的类型,导致程序出问题。
// 在使用类的时候,比如创建类的对象,将类型参数替换为实际的类型,即可。
案例:新生报到,每个学生来自不同的地方,id可能是Int,可能是String
class Student[**T**](val localId: T) { // 在类参数中使用类型参数
def getSchoolId(hukouId: T) = "S-" + hukouId + "-" + localId // 在方法参数中使用类型参数
}
val leo = new Student[Int](111)
// Scala自动推断泛型类型特性:直接给使用了泛型类型的field赋值时,Scala会自动进行类型推断。
scala> val leo = new Student(111)
leo: Student[Int] = Student@f001896
scala> val leo = new Student("string")
leo: Student[String] = Student@488eb7f2
泛型函数
// 泛型函数(方法声明时方法名后面中括号中的即为类型参数),与泛型类类似,可以给某个函数在声明时指定泛型类型,然后在函数体内,多个变量或者返回值之间,就可以使用泛型类型进行声明,从而对某个特殊的变量,或者多个变量,进行强制性的类型限制。
案例:卡片售卖机,可以指定卡片的内容,内容可以是String类型或Int类型
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
}
getCard[String]("hello world")
getCard[Double](0.01)
// 与泛型类一样,你可以通过给使用了泛型类型的变量传递值来让Scala自动推断泛型的实际类型,也可以在调用函数时,手动指定泛型类型,上面就是在调用时手动在中括号中指定的,下面靠传入值自动推断:
scala> getCard ("hello world")
res2: String = card: this is your card, hello world
scala> getCard (0.01)
res3: String = card: 0.01
上边界Bounds
// 在指定泛型类型的时候,有时,我们需要对泛型类型的范围进行界定,而不是可以是任意的类型。比如,我们可能要求某个泛型类型,它就必须是某个类的子类,这样在程序中就可以放心地调用泛型类型继承的父类的方法,程序才能正常的使用和运行。此时就可以使用上下边界Bounds的特性。如下面没有使用上边界时,是不能调用类型参数相关方法的:
class Person(val name: String) {
def makeFriends(p: Person) {}
}
class Party[T](p1: T, p2: T) {
def play = p1.makeFriends(p2) // 编译会出错
}
// Scala的上下边界特性允许泛型类型必须是某个类的子类,或者必须是某个类的父类
案例:在派对上交朋友
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) { // <:要求T必须是Person或其子类,由于p1 类型为Person或其子类Student,所以可以调用父类中的方法
def play = p1.makeFriends(p2)
}
val leo = new Student("Leo")
val lily = new Student("Lily")
new Party(leo,lily).play
*下边界Bounds
// 除了指定泛型类型的上边界,还可以指定下边界,即指定泛型类型必须是某个类的父类
案例:领身份证(只能是自己或父亲代领)
class Father(val name: String)
class Child(name: String) extends Father(name)
def getIDCard[R **>: **Child](person: R) { // R必须是Child的父类
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.")
}
val f = new Father("Father")
val c = new Child("Child")
scala> getIDCard[Father](f)
sign your name for your child's id card.
scala> getIDCard[Child](c)
please tell us your parents' names.
scala> getIDCard(f) // 类型自动推断
sign your name for your child's id card.
scala> getIDCard(c) // 类型自动推断
please tell us your parents' names.
val s = new Student("Student")
scala> getIDCard(s)
sorry, you are not allowed to get id card.
注:下边界与上边界是不同的,上边界是为了可以调用类型参数相应方法,而下边界是为了限制泛型类或泛型函数只适用于哪些类,而不是为了调用类型参数相应方法。
View Bounds
// 上下边界Bounds,虽然可以让一种泛型类型,支持有父子关系的多种类型。但是,在某个类与上下边界Bounds指定的父子类型范围内的类都没有任何关系,则默认是肯定不能接受的。
// 然而,View Bounds作为一种上下边界Bounds的加强版,支持可以对类型进行隐式转换,将指定的类型进行隐式转换后,再判断是否在边界指定的类型范围内
案例:跟小狗交朋友
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(o: Object): Person =
if(o.isInstanceOf[Dog]) {println("-D-");val _o = o.asInstanceOf[Dog]; new Person(_o.name){
override def sayHello = _o.sayHello
} }// 如果是狗,隐式的转换为人
//注:即使是Person或Student,也要将Object强转成Person后返回,而不能直接将o返回,否则可能引起循环调用隐式转换
else if(o.isInstanceOf[Person]) {println("-P-");val _o = o.asInstanceOf[Person];_o} // 如果是人,不用转换,只是强转型后返回
else {println("-O-");error(o.toString)} //其他情况返回Nothing
class Party[T **<%** Person](p1: T){ // <%表示T可以是Person或其子类,或者是可以经过隐式转换后成为Person或其子类的类
def play() = {println(p1.name);println("--------------------")}
}
class Party2[T **<%** Person](p1: T,p2: T){
def play() = {p1.makeFriends(p2);println("--------------------")}
}
val leo = new Person("Leo")
val lily = new Student("Lily")
new Party(leo).play()
new Party(lily).play()
val dog = new Dog("Dog")
new Party(dog).play()//发生隐式转换
new Party2(lily,leo).play()
new Party2(dog,leo).play()//发生隐式转换
new Party2(lily,dog).play()//发生隐式转换
Context Bounds
// Context Bounds是一种特殊的Bounds,它会根据泛型类型的声明,比如“T: 类型”要求必须存在一个类型为“类型[T]”的隐式值(运行时Scala会帮我们自动注入这个已存在的隐式值)。其实个人认为,Context Bounds之所以叫Context,是因为它基于的是一种全局的上下文,需要使用到上下文中的隐式值以及注入。
案例:使用Scala内置的比较器比较大小
// Ordering[T]类似Java中的Comparator比较器
class Calculator[T: Ordering] (val number1: T, val number2: T) {
//运行时,Scala会在上下文中去找类型为Ordering[T]的隐式值并注进来,所以调用该方法时不需要传递该参数值了
def max(implicit order: Ordering[T]) = if(order.compare(number1, number2) > 0) number1 else number2
}
new Calculator(3,4).max
Manifest Context Bounds
// 在Scala中,如果要实例化一个泛型数组,就必须使用Manifest Context Bounds。也就是说,如果数组元素类型为T的话,需要为类或者函数定义[T: Manifest]泛型类型,这样才能实例化Array[T]这种泛型数组。
案例:打包饭菜(一种食品打成一包)
class Meat(val name: String)
class Vegetable(val name: String)
def packageFood[T: Manifest] (food: T*) = {
// 创建泛型数组Array[T]:即数组中的元素类型要在运行时才能确定,编译时无法确定,元素类型为动态
val foodPackage = new Array[T](food.length)
for(i <- 0 until food.length) foodPackage(i) = food(i)
foodPackage
}
val gongbaojiding = new Meat("gongbaojiding")
val yuxiangrousi = new Meat("yuxiangrousi")
val shousiyangpai = new Meat("shousiyangpai")
val meatPackageFood = packageFood(gongbaojiding,yuxiangrousi,shousiyangpai)
meatPackageFood: Array[Meat] = Array(Meat@20b829d5, Meat@7c5f29c6, Meat@4baf997)
val qingcai = new Vegetable("qingcai")
val baicai = new Vegetable("baicai")
val huanggua = new Vegetable("huanggua")
val vegPackageFood = packageFood(qingcai,baicai,huanggua)
vegPackageFood: Array[Vegetable] = Array(Vegetable@583030bd, Vegetable@2ac3d530, Vegetable@2431050d)
协变和逆变
// Scala的协变和逆变是非常有特色的!完全解决了Java中的泛型的一大缺憾!
// 举例来说,Java中,如果有Professional是Master的子类,那么Card[Professionnal]是不是Card[Master]的子类?答案是:不是。因此对于开发程序造成了很多的麻烦。
// 而Scala中,只要灵活使用协变和逆变,就可以解决Java泛型的问题。
案例:进入会场
class Master // 大师
class Professional extends Master // 专家,按理来说,大师是一种专家,应该是Master为Professional子类才对
// 大师以及专家的名片都可以进入会场
class Card[+T] (val name: String) // **协变**:当类型B是类型A的子类型,则可以认为T[B]是T[A]的子类
def enterMeet(card: Card[Master]) {
println("welcome to have this meeting!")
}
// 如果去掉+加号,则Card[Professional]不能传入到enterMeet方法
//专家级别及以上大师级别的名片就可以进入会场
class Card[-T] (val name: String) // 逆变:当类型B是类型A的子类型,则反过来可以认为T[A]是T[B]的子类型
// 要想父类也可以传进来,则要让Card进行逆变,这样Card[Master]就反过来成为Card[Professional]的子类,所以就能传进来了
def enterMeet(card: Card[Professional]) {
println("welcome to have this meeting!")
}
Existential Type
// 在Scala里,有一种特殊的类型参数,就是Existential Type,存在性类型。这种类型务必掌握是什么意思,因为在spark源码实在是太常见了!
Array[T] forSome { type T }
Array[_]
scala> def foo[T](x : Array[T]) = println(x.length)
foo: [T](x: Array[T])Unit
scala> foo(Array[String]("foo", "bar", "baz"))
scala> def foo(x : Array[T] forSome { type T}) = println(x.length)
foo: (x: Array[_])Unit
scala> foo(Array[String]("foo", "bar", "baz"))
scala> def foo(x : Array[_]) = println(x.length)
foo: (x: Array[_])Unit
scala> foo(Array[String]("foo", "bar", "baz"))
scala> def foo(x : Array[T] forSome { type T <: CharSequence}) = x.foreach(y => println(y.length))
foo: (x: Array[_ <: CharSequence])Unit
scala> foo(Array[String]("foo", "bar", "baz"))
隐式转换
Scala提供的隐式转换和隐式参数功能,是非常有特色的功能。是Java等编程语言所没有的功能。它可以允许你手动指定,将某种类型的对象转换成其他类型的对象。通过这些功能,可以实现非常强大,而且特殊的功能。
Scala的隐式转换,其实最核心的就是定义隐式转换函数,即implicit conversion function。定义的隐式转换函数,只要在编写的程序内引入,就会被Scala自动使用。Scala会根据隐式转换函数的签名,在程序中使用到隐式转换函数接收的参数类型定义的对象时,会自动将其传入隐式转换函数,转换为另外一种类型的对象并返回。这就是“隐式转换”。
隐式转换函数叫什么名字是无所谓的,因为通常不会由用户手动调用,而是由Scala进行调用。但是如果要使用隐式转换,则需要对隐式转换函数进行导入。因此通常建议将隐式转换函数的名称命名为“one2one”的形式。
Spark源码中有大量的隐式转换和隐式参数,因此必须精通这种语法。
// 要实现隐式转换,只要程序可见的范围内定义隐式转换函数即可。Scala会自动使用隐式转换函数。隐式转换函数与普通函数唯一的语法区别就是,要以implicit开头,而且最好要定义函数返回类型。
// 案例:特殊售票窗口(只接受特殊人群,比如学生、老人等)
class SpecialPerson(val name: String)
class Student(val name: String)
class Older(val name: String)
**implicit** def object2SpecialPerson (obj: Object): SpecialPerson = {
if (obj.getClass == classOf[Student]) { val stu = obj.asInstanceOf[Student]; new SpecialPerson(stu.name) }
else if (obj.getClass == classOf[Older]) { val older = obj.asInstanceOf[Older]; new SpecialPerson(older.name) }
else error(obj.toString)
}
var ticketNumber = 0
def buySpecialTicket(p: SpecialPerson) = {//只针对特殊人群卖票,所以如果是学生与老人,则要先转换为特殊人群
ticketNumber += 1
"T-" + ticketNumber
}
class Teacher(val name:String)
scala> val tom = new Teacher("tom")
scala> buySpecialTicket(tom)
java.lang.RuntimeException: Teacher@277f7dd3
at scala.sys.package$.error(package.scala:27)
at scala.Predef$.error(Predef.scala:144)
at .object2SpecialPerson(:17)
... 32 elided
scala> val s = new Student("student")
scala> val o = new Older("older")
scala> buySpecialTicket(s)
res1: String = T-1
scala> buySpecialTicket(o)
res2: String = T-2
使用隐式转换加强现有类型
// 隐式转换非常强大的一个功能,就是可以在不知不觉中加强现有类型的功能(有点像Java中的装饰模式)。也就是说,可以为某个类定义一个加强版的类,并定义互相之间的隐式转换,从而让源类在使用加强版的方法时,由Scala自动进行隐式转换为加强类,然后再调用该方法(如内置的Int 类的加强版 RichInt)。
// 案例:超人变身
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默认有两种找隐式转换的方式:首先是从源类型或者目标类型,这两类型的伴生对象中找隐式转换函数;然后在当前程序作用域内找隐式转换函数。
// 如果隐式转换函数不在上述两种情况下的话,那么就必须手动使用import语法引入某个包下的隐式转换函数,比如import test._。通常建议,仅仅在需要进行隐式转换的地方,比如某个函数或者方法内,用import导入隐式转换函数,这样可以缩小隐式转换函数的作用域,避免不需要的隐式转换。
隐式转换的发生时机
// 1、调用某个函数,但是给函数传入的参数的类型,与函数定义的接收参数类型不匹配(案例:特殊售票窗口)
// 2、使用某个类型的对象,调用某个方法,而这个方法并不存在于该类型时(案例:超人变身)
// 3、使用某个类型的对象,调用某个方法,虽然该类型有这个方法,但是传给方法的参数类型与方法定义的接收参数的类型不匹配(案例:特殊售票窗口加强版)
//4、将一种类型赋值给另一种类型时,如果类型不兼容时
// 案例:特殊售票窗口加强版
class TicketHouse {
var ticketNumber = 0
def buySpecialTicket(p: SpecialPerson) = {
ticketNumber += 1
"T-" + ticketNumber
}
}
new TicketHouse().buySpecialTicket(new Student("student"))
隐式参数
// 所谓的隐式参数,指的是在函数或者方法中,定义一个用implicit修饰的参数,此时Scala会尝试找到一个指定类型的,用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.")
}
Actor
Scala的Actor类似于Java中的多线程编程。但是不同的是,Scala的Actor提供的模型与多线程有所不同。Scala的Actor尽可能地避免锁和共享状态,从而避免多线程并发时出现资源争用的情况,进而提升多线程编程的性能。此外,Scala Actor的这种模型还可以避免死锁等一系列传统多线程编程的问题。
Spark中使用的分布式多线程框架,是Akka。Akka也实现了类似Scala Actor的模型,其核心概念同样也是Actor。因此只要掌握了Scala Actor,那么在Spark源码研究时,至少即可看明白Akka Actor相关的代码。但是,换一句话说,由于Spark内部有大量的Akka Actor的使用,因此对于Scala Actor也至少必须掌握,这样才能学习Spark源码。
Actor的创建、启动和消息收发
// Scala提供了Actor trait来让我们更方便地进行actor多线程编程,就Actor trait就类似于Java中的Thread和Runnable一样,是基础的多线程基类和接口。我们只要重写Actor trait的act方法,即可实现自己的线程执行体,与Java中重写run方法类似。
// 此外,使用start()方法启动actor;使用!符号,向actor发送消息;actor内部使用receive和模式匹配接收消息
// 案例:Actor Hello World
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"
收发case class类型的消息
// Scala的Actor模型与Java的多线程模型之间,很大的一个区别就是,Scala Actor天然支持线程之间的精准通信;即一个actor可以给其他actor直接发送消息。这个功能是非常强大和方便的。
// 要给一个actor发送消息,需要使用“actor ! 消息”的语法。在scala中,通常建议使用样例类,即case class来作为消息进行发送。然后在actor接收消息之后,可以使用scala强大的模式匹配功能来进行不同消息的处理。
// 案例:用户注册登录后台接口
import scala.actors.Actor
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")
Actor之间互相收发消息
// 如果两个Actor之间要互相收发消息,那么scala的建议是,一个actor向另外一个actor发送消息时,同时带上自己的引用;其他actor收到自己的消息时,直接通过发送消息的actor的引用,即可以给它回复消息。
// 案例:打电话
import scala.actors.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)
}
}
}
val leoTel = new LeoTelephoneActor
val jackTel = new JackTelephoneActor(leoTel)
leoTel.start
jackTel.start
同步消息和Future
// 默认情况下,消息都是异步的;但是如果希望发送的消息是同步的,即对方接受后,一定要给自己返回结果,那么可以使用!?的方式发送消息。即val reply = actor !? message。
// 如果要异步发送一个消息,但是在后续要获得消息的返回值,那么可以使用Future。即!!语法。val future = actor !! message。val reply = future()。