1.7 条件控制与循环
scala
没有for
循环,只能使用while
循环替代for
循环,或者使用简易版for
循环
var n=10;for (i <- 1 to 10) println(i)
或者使用until
,左闭右开 for (i <- 1 until 10) println(i)
增强for循环
for (c <- "hello") print(i + " ")
跳出循环语句
scala
没有break
语句
import scala.utils.control.Breaks._
多重for循环
for (i <- 1 to 9; j <- 1 to 9){
if (j == 9){
println(i*j)
}
else{
print(i*j + " ")
}
}
if 守卫
for (i <- 1 to 20 if i%2 ==0) println(i)
for 推导式
for (i <- 10) yield i
输入和输出
val name = readLine("Welcome to Game House, Please tell me your name:")
print("Thanks. Then please tell me your age: ")
val age = readInt()
if (age > 18){
printf("Hi, %s, you are %d years old, so you are legal to come here!", name, age)
}else{
printf("Sorry, boy, %s, you are only %d years old. you are illegal to come here!", name, age)
}
1.9 函数入门
def sayHello(name:String) = printf("hello %s!", name)
def fab(n: Int): Int = {
if (n <=0 ) 1
else fab(n-1) + fab(n-2)
}
默认参数
def sayHello(firstName: String, lastName: String = "xixi") = printf(firstName+" "+lastName)
带名参数
sayHello(lastName = "hehe", firstName = "oh")
可以不考虑参数顺序
变长参数
def sum(nums: Int*) = {
var result = 0
for (num <- nums){
result += num
}
result
}
sum(1, 2, 3, 4, 5)
<u>使用序列调用变长参数</u>**
// 递归
def sum(nums: Int*): Int = { // Int*是变长参数
if (nums.length == 0) 0
else nums.head + sum2(nums.tail:_*) //nums.tail: WrappedArray(2,3,4,5)
}
val s = sum(1 to 5) //错误!!! 1 to 5类型是range
val s = sum(1 to 5:_*) // res0: Int = 15
**过程 **
过程: 定义函数时函数体没有用=连接,返回值类型为Unit
def sayHello(name: String) {print(xxx)}
def sayHello(name: String) = "Hello, " +name
def sayHello(name: String): Unit = "Hello, " +name
Lazy值
Lazy值: 只有在第一次使用该变量时,变量对应的表达式才会发生计算(RDD)
lazy val lines = fromFile(filePath).mkString
异常
try{
throw new IllegalArgumentException("x should not be negative")
}catch{
case _: IllegalArgumentException => print("sorry, error!")
}finally{
print("release io resource")
}
1.13 数组操作
Array
scala中Array代表的含义与Java类似,是长度不可变的数组
scala数组的底层实际上是Java数组
val a = new Array[Int](10) //初始化为0
val a = new Array[String](10) //初始化为null
val a = Array("aa", 30) // 类型为Any
a(0) = 1
ArrayBuffer
import scala.collection.mutable.ArrayBuffer
val b = ArrayBuffer[Int]()
b += 1
b += (2,3,4,5) // 重要!!! 加多个元素 b:ArrayBuffer(1,2,3,4,5)
b ++= Array(6,7,8) // ++=操作符 可以添加其他集合中的所有元素 b:ArrayBuffer(1,2,3,4,5,6,7,8)
b.trimEnd(5) // 从尾部开始数5个截断 (1,2,3)
b.insert(1,6) // 在位置1插入6 (从0开始数) 也可以同时插入多个数b.insert(1,6,7,8)
b.remove(1,3) // 从位置1开始移除掉3个元素
Array与ArrayBuffer相互转换
val a = b.toArray
val b = a.toBuffer
遍历Array和ArrayBuffer
for (i <- 0 until b.length) println(b(i)) // 注意这里是until不能用to 否则下标越界
// 跳跃遍历
for (i <- 0 until (b.length, 2) println(b(i)) // 隔2个遍历 0,2,4,6,8
// 从尾部遍历
for (i <- (0 until b.length).reverse) println(b(i))
// 增强for循环
for (e <- b) println(e)
数组其他操作
val a = Array(1,2,3,4,5)
val sum = a.sum
val max = a.max
scala.util.Sorting.quickSort(a)
a.mkString() // String = 12345
a.mkString(",") // String = 1,2,3,4,5
a.mkString("<",",",">") // String = <1,2,3,4,5>
数组转换
// 1. 使用yield转换 类型不变 Array转换后还是Array
val a = Array(1,2,3,4,5)
val a2 = for(e<-a) yield e*e // Array(1,4,9,16,25)
val b = ArrayBuffer[Int]()
b += (1,2,3,4,5)
val b2 = for (e <- b if e%2 == 0) yield e*e // b2: ArrayBuffer
// 2. 使用函数式编程转换数组
a.filter(_%2==0).map(2*_)
a.filter{_%2==0}.map{2*_}
算法案例
// 移除第一个负数之后的所有负数
val a = ArrayBuffer[Int]()
a += (1,2,3,4,5,-1,-3,-7,-11)
var foundFirstNegative = false
var arrayLen = a.length
var index = 0
while(index<arrayLen){
if (a(index)>=0){
index +=1
}
else{
if (!foundFirstNegative){foundFirstNegative = true;index += 1} // 第一个负数不移除
else {a.remove(index);arrayLen -= 1}
}
}
这个算法的问题在于,发现了一个负数就要remove一次,则整个数组往前移一位,性能较差$o(n^2)$
// 优化:记录索引,一次性移除
val a = ArrayBuffer[Int]()
a += (1,2,3,4,5,-1,-3,-7,-11)
var foundFirstNegative = false
val keepIndexes = for (i<-0 until a.length if !foundFirstNegative || a(i)>=0) yield{
if (a(i) < 0) foundFirstNegative = true
i
}
for (i <- 0 until keepIndexes.length) a(i) = a(keepIndexes(i)) // 把要保留的元素全部移到了a的前面
a.trimEnd(a.length - keepIndexes.length) // 把后面的负数截断
1.16 Map与Tuple
创建Map
val ages = Map("Leo" -> 30, "Jen" -> 21)
ages("Leo") = 21 // 默认为immutable 不可修改
val ages = scala.collenction.mutable.Map("Leo" -> 30, "Jen" -> 21)
// 另一种方式定义Map元素
val ages = Map(("Leo", 30),("Jen", 21))
// 创建一个空的HashMap,但不能创建一个空的Map
val ages = new scala.collection.mutable.HashMap[String, Int]
访问Map元素
val leoAge = ages("leo")
val leoAge = if(ages.contains("Leo")) ages("Leo") else 0
val leoAge = ages.getOrElse("Leo", 0)
修改Map元素
ages("Leo") = 31
ages += ("Mike" -> 35, "Tom" -> 30)
ages -= "Mike"
// 更新immutable的map
val ages2 = ages + ("Mike" -> 35, "Tom" -> 30) // ages2是可变的
val ages3 = ages - "Tom"
val ages2 = ages + ("Leo" -> 31) //【注意】这里会自动对原有元素Leo的值进行修改,而不是增加新的元素
遍历Map元素
for ((key, value) <- ages) println(key, value)
for (key <- ages.keySet) println(key)
for (value <- ages.values) println(value)
for ((key, value) <- ages) yield(value, key) // 反转key和value
SortedMap和LinkedHashMap
// SortedMap可以自动对Map的key排序
val ages = scala.collection.immutable.SortedMap("leo"->30, "alice"->15, "jen"->25)
ages.keySet // Set(alice, jen, leo)
// LinkedHashMap可以记住插入的顺序
val ages = new scala.collection.mutable.LinkedHashMap[String, Int]()
ages("leo") = 30
ages("alice") = 21
Tuple
val t = ("leo", 30)
t._1 // 下标从1开始
val names = Array("leo", "jack", "mike")
val ages = Array(30, 24, 26)
val nameAges = names.zip(ages) // Array((leo, 30), (jack, 24), (mike, 26)) 元素为Tuple
for ((name, age) <- nameAges) println(name +":"+ age)
1.17 面向对象编程
定义一个简单的类
class HelloWorld{
private var name = "leo"
def sayHello() = {println("hello! "+name)}
def getName = name
}
val hw = new HelloWorld
hw.name // 不行,因为是private,只能在类里使用
hw.sayHello()
hw.getName // 不能用getName(),因为定义时没有()
getter与setter
// 定义不带private的var field,此时scala生成的面向JVM的类时,会定义为private的name字段,并提供public的getter和setter方法
// 如果使用private修饰field, 则生成的getter和setter也是private的
// 如果定义val field,则只会生成getter方法
// 如果不希望生成setter和getter方法,则将field声明为private[this]
class Student{
var name = "leo" // private 自动生成getter和setter
}
//调用getter和setter方法,分别叫做name和name_=
val leo = new Student
print(leo.name)
leo.name = "leo1" // 调用name_=方法重新set name
自定义getter与setter
class Student{
private var myName = "leo"
def name = "your name is "+myName
def name_=(newValue: String){ // name_= 等号不能有空格
print("\nyou cannot edit your name")
}
}
仅暴露field的getter方法
class Student{
private var myName = "leo"
def updateName(newName: String){
if(newName == "leo1") myName = newName
else print("not accept this new name!")
}
def name = "your name is "+myName
}
private[this]的使用
class Student{
private var myAge = 0
def age_=(newValue: Int){
if(newValue > 0) myAge = newValue
else print("illegal age!")
}
def age = myAge
def older(s: Student) = {
myAge > s.myAge
}
}
val s1 = new Student
s1.age = 20
val s2 = new Student
s2.age = 25
s1.older(s2)
Java风格的getter和setter 加上注解 @BeanProperty var name: String = _
protected
// 用protected修饰的field和method,在子类中不需要用super关键字,可以直接访问
// 还可以使用protected[this],则只能在当前子类对象中访问父类的field和method,无法通过其他子类对象访问父类的field和method
class Person{
protected var name: String = "leo"
protected[this] var hobby: String = "game"
}
class Student extends Person{
def sayHello = println("hello"+name)
def makeFriends(s: Student){
println("my hobby is "+hobby+", your hobby is "+ s.hobby) // error: value hobby is not a member of Student
}
}
辅助constructor
class Student{
private var name = ""
private var age = 0
def this(name: String){
this()
this.name = name
}
def this(name: String, age: Int){
this(name)
this.age = age
}
}
主constructor
// Scala中,主constructor是与类名放在一起的,与java不同
// 而且类中,没有定义在任何方法或者代码块之中的代码,就是主constructor的代码
class Student(val name: String, val age: Int){
println("your name is "+name+", your age is "+age)
}
// 主构造参数也可以有默认参数
class Student(val name: String = "leo", val age: Int = 18){
println("your name is "+name+", your age is "+age)
}
val s = new Student
// 如果主constructor传入的参数没有修饰,如name: String,如果类内部的方法使用到了,那么会声明为private[this] name; 否则没有该field,就只能被constructor代码使用
调用父类的constructor
// scala中,每个类可以有一个主constructor和多个辅助constructor,而每个辅助的第一行都必须是调用其他辅助constructor或者主constructor,因此子类的辅助constructor是一定不可能直接调用父类的constructor的
// 只能在子类的主constructor中调用父类的constructor => Person(name, age)
// 【注意】如果是父类中接收的参数,比如name和age,子类中接收时,就不要用任何val或者var来修饰,否则会任务是子类要覆盖父类的field
class Person(val name: String, val age: Int){
}
class Student(name: String, age: Int, var score: Double) extends Person(name, age){
def this(name: String){
this(name, 0, 0)
}
def this(age: Int){
this("leo", age, 0)
}
}
内部类
import scala.collection.mutable.ArrayBuffer
class Class{
class Student(name: String){}
val students = new ArrayBuffer[Student]
def getStudent(name: String) = {
new Student(name)
}
}
val c1 = new Class
val s1 = c1.getStudent("leo")
c1.students += s1
/*************/
val c2 = new Class
val s2 = c2.getStudent("leo")
c1.students += s2 // [error]type mismatch s2是c2外部类的Student实例,c1无法获得
object
// object相当于class的单个实例,通常放一些静态的field或者method,第一次调用object的方法时,就会执行object的constructor,也就是object内部不在method中的代码,但是object不能定义接受参数的constructor
// 注意,object的constructor只会在第一次被调用时执行,以后不会再次执行
// object通常用于作为单例模式的实现,或者放class的静态成员,比如工具方法
object Person{
private eyeNum = 2
println("this Person object")
def getEyeNum = eyeNum
}
/*******让object继承抽象类*******/
abstract class Hello(var msg:String){
def sayHello(name: String): Unit
}
object HelloImpl extends Hello("hello"){
override def sayHello(name: String) = {
println(msg+", "+name)
}
}
HelloImpl.sayHello("leo")
/******用object实现枚举功能******/
object Season extends Enumeration{
val SPRING, SUMMER, AUTUMN, WINTER = Value // 调用Value方法来初始化枚举值
}
Season.WINTER // Seaon.Value = WINTER
// 还可以用过Value传入枚举值的id和name,
object Season extends Enumeration{
val SPRING = Value(0, "Spring")
val SUMMER = Value(1, "SUMMER")
val AUTUMN = Value(2, "Autumn")
val WINTER = Value(3, "winter")
}
Season.SPRING.id
Season.SPRING.toString
// 可以反过来通过id和name找到枚举值
Season(0)
Season.withName("Spring")
// 增强for循环之间打印所有枚举值
for (ele <- Season.values) prinln(ele)
apply方法
// object中非常重要的一个特殊方法就是apply方法,通常在object中实现apply方法,并在其中实现构造object对象的功能
// 而创建伴生类的对象时,通常不会使用new class的方式,而是使用Class()的方式,隐式地调用伴生对象的apply方法,这样会让对象创建更加简洁,如 val a = Array(1,2,3,4,5)
class Person(val name:String)
object Person{
def apply(name:String) = new Person(name)
}
val p = Person("leo")
main方法
// scala的main方法必须在object中实现
// App Trait的工作原理为:App Trait继承自DelayedInit Trait,scalac命令进行编译时,会把继承App Trait的object的constructor代码都放到DelayedInit Trait的delayedInit方法中执行
object HelloWorld extends App{
if (args.length > 0) println("Hello, "+args(0))
else println("Hello World!")
}
// shell
scalac HelloWorld.scala
scala HelloWorld leo
scala -Dscala.time HelloWorld // 同时显示程序运行时间
继承
class Person{
private var name = "leo"
def getName = name
}
class Student extends Person{
private var score = "A"
def getScore = score
}
/**override和super**/
class Person{
private var name = "leo"
def getName = name
}
class Student extends Person{
private var score = "A"
def getScore = score
override def getName = "hi i'm " + super.getName // 注意用super
}
/**override field**/
class Person{
val name: String = "Person"
def age: Int = 0
}
class Student extends Person{
override val name: String = "leo"
override val age: Int = 30 // 覆盖的是def方法
}
isInstanceOf和asInstanceOf
// 如果我们创建了子类的对象,又将其赋予了父类类型的变量。则在后续程序中,我们又需要将父类类型的变量转换为子类类型的变量,应该如何做?
// 首先,需要使用isInstanceOf判断对象是否是指定类的对象,若是,则可以用asInstanceOf将对象转换为指定类型
// 注意,如果对象是null,则isInstanceOf一定返回false,asInstanceOf一定返回null
// 注意,如果没有用isInstanceOf先判断对象是否为指定类的实例,就直接用asInstanceOf转换,则可能会抛出异常
class Person
class Student extends Person
val p: Person = new Student
val s: Student = null
if (p.isInstanceOf[Student]) s = p.asInstanceOf[Student]
getClass和classOf
// 对象.getClass可以精确获取对象的类,classOf[类]可以精确获取类,然后使用==操作符即可判断
class Person
class Student extends Person
val p: Person = new Student
p.isInstanceOf[Person] //true
p.isInstanceOf[Student] //true
p.getClass == classOf[Person] // false
p.getClass == classOf[Student] // true
使用模式匹配进行类型判断
// 类似isInstanceOf,不是精确判断
p match{
case per: Person => println("person")
case _ => println("unknown type")
}
匿名子类
// 匿名内部类,就是说可以定义一个类的没有名称的子类,并直接创建其对象,然后将对象的引用赋予一个变量。之后甚至可以将该匿名子类的对象传递给其他函数。
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)
}
greeting(p) // hi i'm leo
抽象类
abstract class Person(val name: String){
def sayHello: Unit
}
class Student(name: String) extends Person{
def sayHello: Unit = println("hello "+name) // 不用override
}
// 抽象field,在父类中定义了field,但没有给出初始值,则此field为抽象field
abstract class Person{
val name: String
}
class Student extends Person{
val name: String = "leo"
}
Trait
将Trait作为接口使用
// 类继承trait后,必须实现其中的抽象方法,实现时不需要使用override
// scala不支持对类进行多重继承,但可以用with继承多个trait
trait SayHello{
def sayHello(name: String)
}
trait MakeFriends{
def makeFriends(p: Person)
}
class Person(val name: String) extends SayHello with MakeFriends{
def sayHello(name: String) = println("hello "+ name)
def makeFriends(p: Person) = println("hi " + p.name)
}
val p1 = new Person("leo")
val p2 = new Person("anna")
p1.makeFriends(p2)
在Trait中定义具体方法/字段
// Trait中可以包含一些很多类都通用的方法,比如打印日志等,spark中就使用了Logger trait
trait Logger{
def log(msg: String) = println(msg)
}
class Person(val name: String) extends Logger{
def makeFriends(p: Person){
println("hi i'm "+ name +", nice to meet you "+ p.name)
log("makeFriends method is invoked with parameter Person[name="+p.name+"]")
}
}
// 定义字段
trait Person{
val eyeNum: Int = 2
}
class Student(val name: String) extends Person{
def sayHello = println("hi i'm "+ name + ", i have " + eyeNum + " eyes.")
}
在Trait中定义抽象字段
trait SayHello{
val msg: String
def sayHello(name: String) = println(msg+", "+name)
}
class Person(val name: String) extends SayHello{
val msg: String = "hello"
def makeFriends(p: Person){
sayHello(p.name)
println("I'm "+name+"!")
}
}
为实例混入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 // 动态地混入trait
trait调用链
// scala中支持让类继承多个trait后,依次调用多个trait中的同一个方法,只要让多个trait的同一个方法中,在最后都执行super即可
// 类中调用多个trait中都有的这个方法时,首先会从最右边的trait方法开始执行,然后依次往左执行,形成一个调用链条
// 这种特性非常强大,其实就相当于设计模式中的责任链模式的一种具体实现依赖
trait Handler{
def handle(data: String){
}
}
trait DataValidHandler extends Handler{
override def handle(data: String){ // 覆盖Handler的handle方法
println("check data: "+data) // 自己的逻辑
super.handle(data) // 调用下一个trait
}
}
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)}
}
在trait中覆盖抽象方法
// 覆盖时,如果使用了super.方法的代码,则无法通过编译,因为super.方法就会去掉父trait的抽象方法,此时子trait的该方法还是会被认为是抽象的
// 如果此时要通过编译,就得给子trait的方法加上abstract override修饰
trait Logger{
def log(msg: String)
}
trait MyLogger extends Logger{
abstract override def log(msg: String) {super.log(msg)}
}
混合使用trait的具体方法和抽象方法
// 在trait中可以混合使用trait的具体方法和抽象方法
// 可以让具体方法依赖于抽象方法,而抽象方法则放到继承trait的类中去实现
// 这种trait其实就是设计模式中的模板设计模式的体现
trait Valid{
def getName: String // 抽象方法
def valid: Boolean = {
getName == "leo"
}
}
class Person(val name: String) extends Valid{
println(valid)
def getName = name
}
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()
/**
Person's constructor!
Logger's constructor!
MyLogger's constructor!
TimeLogger's constructor!
Student's constructor!
*/
trait field的初始化
// scala中,trait是没有接收参数的构造函数的,这是trait与class的唯一区别,但是如果需求是要trait能够对field进行初始化,就只能用提前定义
trait SayHello{
val msg: String
println(msg.toString)
}
class Person extends SayHello{
val msg: String = "init"
}
val p = new Person // error: java.lang.NullPointException
// 过程: p先构造父类(没有父类),再构造trait,此时由于msg没初始化msg.toString肯定是空指针
/***提前定义***/
class Person
val p = new{
val msg: String = "null"
} with Person with SayHello // 动态混入
class Person extends {
val msg: String = "init" // 因为要先构造父类
} with SayHello()
/***lazy value***/
trait SayHello{
lazy val msg: String = null
println(msg.toString)
}
class Person extends SayHello{
override lazy val msg: String = "init"
}
trait继承class
class MyUtil{
def printMsg(msg: String) = println(msg)
}
trait Logger extends MyUtil {
def log(msg: String) = printMsg("log: "+msg)
}
class Person(val name: String) extends Logger{
def sayHello{
log("hi i'm" + name)
printMsg("hi i'm" + name)
}
}
1.24 函数式编程
将函数赋值给变量
// scala中可以将函数作为值赋值为变量
// scala语法规定,将函数作为值赋值为变量时,必须在函数后加上空格和下划线
def sayHello(name: String) = println("hello! "+name)
val sayHelloFunc = sayHello _
sayHelloFunc("leo")
匿名函数
// 匿名函数:函数不需要命名
// 可以直接定义函数之后,将函数赋值给某个变量;也可以将直接定义的匿名函数传入其他函数之中
// (参数名: 参数类型) => 函数体
val sayHelloFunc = (name: String) => println("hello, "+name)
sayHelloFunc("leo")
高阶函数
// scala中可以直接将某个函数传入其他函数,作为参数。是Java不具备的。
// 接收其他函数作为参数的函数,称作高阶函数
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)
// 函数作为返回值
def getGreetingFunc(msg: String) = (name: String) => println(msg+", "+name)
val greetingFunc = getGreetingFunc("hello")
greetingFunc("leo") // hello, leo
高阶函数的类型推断
// 高阶函数可以自动推断出参数类型,而不需要写明类型;而且对于只有一个参数的函数,还可以省去其小括号;如果仅有一个参数在右侧的函数体内只使用一次,还可以将接收参数省略,并且将参数用_来替代
def greeting(func: (String) => Unit, name: String) {func(name)}
greeting((name) => println("hello "+name), "leo") // name的String类型可以不用写了
greeting(name => println("hello "+name), "leo") // name的()可以不用写了
greeting(_ => println("hello "+_), "leo")
def triple(func: (Int)=>Int) = {func(3)}
triple(5+_) // 8
scala的常用高阶函数
// map
Array(1, 2, 3).map(2 * _)
// foreach: 对传入的每个元素都进行处理,但是没有返回值
(1 to 9).map("*" * _).foreach(println _)
// filter: 对传入的每个元素都进行条件判断
(1 to 20).filter(_ % 2 == 0)
// reduceLeft: 从左侧开始,进行reduce操作
(1 to 9).reduceLeft(_ * _)
//sortWith: 对元素进行两两相比,进行排序
Array(3,2,5,4,10,1).sortWith(_ < _) // Array(1,2,3,4,5,10)
闭包
// 函数在变量不处于其有效作用域时,还能够对变量进行访问,即为闭包
def getGreetingFunc(msg: String) = (name: String) => println(msg+", "+name)
var greetingFuncHello = getGreetingFunc("hello")
val greetingFuncHi = getGreetingFunc("hi")
// msg只是一个局部变量,确诊getGreetingFunc执行完之后,还可以继续存在创建的函数之中,greetingFuncHello("leo")调用时,值为"hello"的msg仍保留在了返回的函数体的内部,可以反复的使用
// 这种变量超出了其作用域,还可以使用的情况,即为闭包
// scala通过给每个函数创建对象来实现闭包,实际上对于getGreetingFunc函数创建的函数,msg是作为函数对象的变量存在的,因此每个函数才可以拥有不同的msg
SAM转换
// SAM-single abstract method
// 在Java中,不支持直接将函数传入一个方法作为参数,通常来说,唯一的办法就是定义一个实现了某个接口的类的实例对象,该对象只有一个方法;而这些接口都只有单个的抽象方法,也就是SAM
// 在调用Java方法时,使用的功能,SAM转换,即将SAM转换为Scala函数
// 要使用SAM转换,需要使用Scala提供的特性,隐式转换
import javax.swing._
import java.awt.event._
val button = new JButton("Click")
button.addActionListener(new ActionListener{
override def actionPerformed(event: ActionEvent){
println("Click Me!!")
}
})
// 隐式转换
// 接收参数为ActionEvent,返回值为Unit的匿名函数
implicit def convertActionListener(actionProcessFunc:(ActionEvent)=>Unit) = new ActionListener{
override def actionPerformed(event:ActionEvent){
actionProcessFunc(event)
}
}
button.addActionListener((event: ActionEvent) => println("Click Me!!"))
Currying 柯里函数
// 把原来接收两个参数的一个函数,转换为两个函数,第一个函数接收原先的第一个参数,然后返回接收原先第二个参数的第二个函数。
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
return
// Scala中,不需要使用return来返回函数的值,使用return的匿名函数,是必须给出返回类型的,否则无法通过编译
def greeting(name: String) = {
def sayHello(name:String): String = {
return "Hello, " + name
}
sayHello(name)
}
1.26 集合
List
immutable
val list = List(1,2,3,4)
list.head // Int = 1
list.tail // List(2,3,4)
// List的 :: 操作方法,可以用于将head和tail合并成一个List, 0::List = List(0,1,2,3,4)
// 案例:用递归函数来给List中的每个元素都加上指定前缀并打印
def decorator(list: List[Int], prefix: String){
if (list != Nil){
println(prefix + list.head)
decorator(list.tail, prefix) // 当list = (4)时,list.tail = Nil
}
}
LinkedList
val ll = scala.collection.mutable.LinkedList(1,2,3,4,5)
ll.elem // 头元素,类似head
ll.next // 类似tail
// 案例:使用while循环将LinkedList的每个元素*2
var currentList = ll // 【注意】对currentList的元素操作,ll的元素也随之改变
while (currentList != Nil){
currentList.elem = currentList.elem*2
currentList = currenList.next
}
// 使用while循环将LinkedList每隔一个元素*2
var ll = scala.collection.mutable.LinkedList(1,2,3,4,5,6,7,8,9,10)
var currentList = ll
var fisrt = true
while(currentList != Nil && currentList.next != Nil){ //注意两重判断
if(f) {currentList.elem *= 2;first = false}
currentList = currentList.next.next
if (currentList != Nil) currentList.elem *= 2
}
ll
Set
val s = Set(1,2,3)
s + 1 // Set(1,2,3)
s + 4 // Set(1,2,3,4)
// Set不保证插入顺序
s = new scala.collection.mutable.HashSet[Int](); s+=1;s+=2;s+=3
// LinkedHashSet可以保证插入顺序
val s = scala.collection.mutable.LinkedHashSet[Int]();s+=1;s+=2;s+=3
集合的函数式编程
// Java里面是没有函数式编程的,所以也没有高阶函数,也无法直接将函数传入一个方法,或者让一个方法返回一个函数
List("a","b","c").map("xixi"+_)
List("hello world", "you me").flatMap(_.split(" ")) // List(hello, world, you, me)
List("I","have","an","apple").foreach(println(_))
List("leo", "jen", "jack").zip(List(100,70,80)) // List((leo,100), (jen,70), (jack,80))
/****单词计数****/
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
val lines = List(lines01, lines02)
lines.flatMap(_.split(" ")).map((_, 1)).map(_._2).reduceLeft(_+_)
1.28 模式匹配
// Scala的模式匹配除了值还可以对类型进行匹配,对Array和List的元素情况进行匹配,对case class进行匹配
// match case里,只要一个分支满足了就不会进行处理了,所以不需要break
def judgeGrade(name: String, grade: String){
grade match{
case "A" => println("A")
case "B" => println("B")
case _ if name == "leo" => println(name + "you are a good boy") // if守卫
case _ => println("o")
}
}
//在模式匹配中进行变量赋值(对于_的情况)
def judgeGrade(grade: String){
grade match{
case "A" => println("A")
case "B" => println("B")
case badGrade => println(badGrade)
}
}
对类型进行模式匹配
import java.io._
def processException(e: Exception){
e match{
case e1: IllegalArgumentException => println(e1)
case e2: FileNotFoundException => println(e2)
case e3: IOException => println(e3)
case _: Exception => println("idk your exception")
}
}
processException(new IOException("This is an IOException!"))
对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") // Array("leo",g1,g2)会被匹配
case Array("leo",_*) => println("hi leo, please introduce your friends")
case _ => println("hey who are you")
}
}
def greeting(list: List[String]){
list match{
case "leo"::Nil => println("hi leo")
case girl1::girl2::girl3::Nil => println("hi girls")
case "leo"::tail => println("hi leo who are they")
case _ => println("who are you")
}
}
case class
// case class样例类,类似JavaBean,只定义field,且由scala编译器自动提供getter和setter方法,但是没有method,case class的主构造函数接收的参数通常不需要使用var或val修饰,scala自动就会使用val修饰
// 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+" "+subject)
case Student(name, classroom) => println("student "+name+" "+classroom)
case _ => println("illegal")
}
}
val leo: Person = Student("leo", "class1")
val tom: Person = Teacher("leo", "Math")
case class Worker(name: String) extends Person
val jack: Person = Worker("jack")
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 no your grade")
}
}
1.30 类型参数
class Student[T](val localID: T){
def getSchoolID(hukouID: T) = "S-"+hukouID+"-"+localID
}
val leo = new Student[Int](111)
leo.getSchoolID("2") // 不行 required:Int
泛型函数
// 泛型函数,与泛型类类似,可以给某个函数在声明时指定泛型类型,然后在函数体内,多个变量或者返回值直接,就可以使用泛型类型进行声明,从而对某个特殊的变量,或者多个变量,进行强制性的类型限制。
// 与泛型类一样,你可以通过给使用了泛型类型的变量传递值来让Scala自动推断泛型的实际类型,也可以在调用函数时,手动指定泛型类型。
/**案例: 卡片售卖机,可以指定卡片的内容,内容可以是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")
上边界Bounds
// 在指定泛型类型的时候,有时,我们需要对泛型类型的范围进行界定,而不是可以是任意的类型,比如,我们可能要求某个泛型类型,它就必须是某个类的子类,这样在程序中就可以放心地调用泛型类型继承的父类的方法,程序才能正常的使用和运行。此时就可以使用上下边界Bounds的特性。
// 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){
def play = p1.makeFriends(p2)
}
class Worker(val name: String)
val leo = new Student("leo")
val tom = new Worker("tom")
val party = new Party(leo, tom) // error
val jack = new Student("jack")
val party = new Party(leo, jack)
party.play()
下边界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("child")
else if (person.getClass == classOf[Father]) println("father")
else println("your are not allowed to getIDCard")
}
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("won won i'm "+name)
}
implicit def dog2person(obj: Object): Person = if(obj.isInstanceOf[Dog]){
val dog = obj.asInstanceOf[Dog]
new Person(dog.name)
}else Nil
class Party[T <% Person](p1: T, p2: T){} // <%
val leo = new Student("leo")
val doggy = new Dog("doggy")
val party = new Party(leo, doggy)
Context Bounds
// Context Bounds是一种特殊的Bounds,它会根据泛型类型的声明,比如“T: 类型”要求必须存在一个类型为“类型[T]”的隐式值。
/***案例:使用Scala内置的比较器比较大小***/
class Calculator[T: Ordering](val num1: T, val num2: T){
def max(implicit order: Ordering[T]) = if(order.compare(num1, num2) > 0) num1 else num2
}
val cal = new Calculator(1,5) // cal: Calculator[Int] = Calculator@2a76840c
cal.max // 5
Manifest Context Bounds
// 在Scala中,如果要实例化一个泛型数组,就必须使用Manifest Context Bounds。也就是说,如果数组元素类型为T的话,需要为类或者函数定义[T:Manifest]泛型类型,这样才能实例化Array[T]这种泛型数组。
/***案例:打包饭菜(一种食品打成一包)***/
class Meat(val name: String)
class Vegetable(val name: String)
def packageFood(foods: T*) = { // foods: T* 变长参数
val foodPackage = new Array[T](foods.length)
for(i <- 0 until foods.length) foodPackage(i) = foods(i)
foodPackage
}
val yuxiangrousi = new Meat("yuxiangrousi")
val yangpai = new Meat("yangpai")
val bocai = new Vegetable("bocai")
packageFood(yuxiangrousi, yangpai) // Array[Object] = Array(Meat@2ca65ce4, Meat@7957dc72, Vegetable@6058e535)
packageFood(yuxiangrousi, yangpai, bocai) // Array[Meat] = Array(Meat@2ca65ce4, Meat@7957dc72)
协变和逆变
// SCala的协变和逆变是非常有特色的,完全解决了Java中的泛型的一大缺憾
// 举例来说,Java中,如果有Professional是Master的子类,那么Card[Professional]不是Card[Master]的子类。而Scala中,只要灵活使用协变和逆变,就可以解决Java泛型的问题。
/***案例:进入会场***/
class Master
class Professional extends Master
// 大师以及大师级别以下的名片都可以进入会场
class Card[+T](val name: String) // +T 协变
def enterMeet(card: Card[Master]){
println("welcomt to this meeting "+ card.name)
}
val leo = new Card[Master]("leo")
val jack = new Card[Professional]("jack")
jack.isInstanceOf[Card[Master]] // true Card[Professional]是Card[Master]的子类
enterMeet(jack) // 可以进入
// 专家及专家以上级别的名片都可以进入会场
class Card[-T](val name: String) // -T 逆变 含义是把Master强制转换为Professional的子类
val leo = new Card[Professional]("leo")
val jack = new Card[Master]("jack")
jack.isInstanceOf[Card[Professional]] // true
def enterMeet(card: Card[Professional]){
println("welcomt to this meeting "+ card.name)
}
enterMeet(jack)
Existential Type
// 在Scala里,有一种特殊的类型参数,就是Existential Type,存在性类型
Array[T] forSome {type T}
Array[_]
1.32 隐式转换和隐式参数
Scala的隐式转换,最核心的就是定义隐式转换参数,即implicit conversion function。定义的隐式转换函数,只要在编写的程序内引入,就会被Scala自动使用。Scala会根据隐式转换函数的签名,在程序中使用到隐式转换接收的参数类型定义的对象时,会自动将其传入隐式转换函数,转换为另外一种类型的对象并返回。这就是“隐式转换”。
隐式转换
// 要实现隐式转换,只要程序可见的范围内定义隐式转换函数即可,Scala会自动使用隐式转换函数。隐式转换函数与普通函数唯一的语法区别就是,要以implicit开头,而且最好要定义函数返回类型。
/***案例:特殊售票窗口(只接受特殊人群,比如学生、老人等)***/
class SpecialPerson(val name: String)
class Student(val name: String)
class Oldman(val name: String)
implicit object2SpecialPerson(obj: Object): SpecialPerson = {
if(obj.getClass == classOf[Student]) {val stu = obj.asInstanceOf[Student]; new SpecialPerson(stu.name)}
else if(obj.getClass == classOf[Oldman]) {val old = obj.asInstanceOf[Oldman]; new SpecialPerson(old.name)}
else Nil
}
var ticketNum = 0
def buySpecialTicket(p: SpecialPerson) = {
ticketNum += 1
"T-"+ticketNum
}
val s = new Student("stu")
buySpecialTicket(s)
使用隐式转换加强现有类型
// 隐式转换非常强大的一个功能,就是可以加强现有类型的功能。也就是说,可以为某个类定义一个加强版的类,并定义互相之间的隐式转换,从而让源类在使用加强版的方法时,有Scala自动进行隐式转换为加强类,然后再调用该方法。
/***案例:超人变身***/
class Man(val name: String)
class Superman(val name: String){
def emitLaser = println("emit laser")
}
implicit def man2superman(man: Man): Superman = new Superman(man.name)
val leo = new Man("leo")
leo.emitLaser
导入隐式转换
Scala默认会使用两种隐式转换,一种是源类型,或者目标类型的伴生对象内的隐式转换函数;一种是当前程序作用域内的可以用唯一标识符表示的隐式转换函数。
如果隐式转换函数不在上述两种情况下的话,那么就必须手动使用import语法引入某个包下的隐式转换函数,比如import test._ 通常建议,仅仅在需要进行隐式转换的地方,比如某个函数或者方法内,用import导入隐式转换函数,避免不需要的隐式转换。
隐式转换的发生时机
- 调用某个函数,但是给函数传入的参数的类型,与函数定义的接收参数类型不匹配
- 使用某个类型的对象,调用某个方法,而这个方法并不存在于该类型时
- 使用某个类型的对象调用某个方法,虽然该类型有这个方法,但是给方法传入的参数类型,与方法定义的接收参数的类型不匹配
/***3. 案例:特殊售票窗口加强版***/
class TicketHouse{
var ticketNum = 0
def buySpecialTicket(p: SpecialPerson) = {
ticketNum += 1
"T-"+ticketNum
}
}
val ticketHouse = new TicketHouse
val leo = new Student("leo")
ticketHouse.buySpecialTicket(leo)
隐式参数
// 所谓的隐式参数,指的是在函数或者方法中,定义一个用implicit修饰的参数,此时Scala会尝试找到一个指定类型的,用implicit修饰的对象,即隐式值,并注入参数。
// Scala会在两个范围内查找:一种是当前作用域内可见的val或var定义的隐式变量;一种是隐式参数类型的伴生对象内的隐式值
/***案例:考试签到***/
class SignPen{
def write(cotent: String) = println(content)
}
implicit val signPen = new SignPen // 签到只需要一只笔!
def signForExam(name: String)(implicit signPen: SignPen){
signPen.write(name+" come to exam in time.")
}
1.33 Actor入门
Actor类似Java的多线程编程,但是又有所不同。Scala的Actor尽可能地避免锁和共享状态,从而避免多线程并发时出现资源争用的情况,进而提升多线程编程的性能。此外,Scala Actor的这种模型还可以避免死锁等一系列传统多线程编程的问题。
Spark中使用的分布式多线程框架,是Akka。Akka也实现了类似Scala Actor的模型,其核心概念同样也是Actor。
Actor的创建、启动和消息收发
// Scala提供了Actor trait来让我们更方便地进行actor多线程编程,就Actor trait就类似于Java中的Thread和Runnable一样,是基础的多线程基类和接口。我们只要重写Actor trait的act方法,即可实现自己的线程执行体,与Java中重写run方法类似。此外,使用start方法启动Actor,使用!向Actor发送消息,Actor使用receive和模式匹配接收消息。
/***案例:Actor Hello World***/
class HelloActor extends Actor{
def act(){
while(true){
receive{case name: String => println("hello "+name)}
}
}
}
val helloActor = new HelloActor
helloActor.start()
helloActor!"leo" // hello leo
收发case class类型的消息
/***案例:用户注册登录后台接口***/
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: "+name+", "+password)
case Register(username, password) => println("register: "+name+", "+password)
}
}
}
}
val userManageActor = new UserManageActor
userManageActor.start()
userManageActor ! Register("leo", "123")
Actor之间互相收发消息
// 如果两个Actor之间要互相收发消息,那么Scala建议一个Actor向另一个Actor发送消息时,同时带上自己的引用;其他Actor收到自己的消息时,直接通过发送消息的actor的引用,即可以给它回复消息。
/***案例:打电话***/
case class Message(content: String, sender: Actor)
class LeoActor extends Actor{
def act(){
while(true){
receive{
case Message(content, sender) => {
println("leo: "+content)
sender!"please call me later." // 注意!不能有空格
}
}
}
}
}
class JackActor(val leoActor: Actor) extends Actor{
def act(){
leoActor!Message("hi i'm jack", this) // 传jackActor自己的引用
receive{
case response: String => println("jack telephone: "+response)
}
}
}
val leoActor = new LeoActor
leoActor.start()
val jackActor = new JackActor(leoActor)
jackActor.start()
同步消息和Future
// 默认情况下,消息都是异步的;但是如果希望发送的消息是同步,即对方接收后,一定要给自己返回结果,那么可以使用!?的方式发送消息。
val reply = actor!?message
// 如果要异步发送一个消息,但是在后续要获得消息的返回值,那么可以使用Future。即!!语法。
val future = actor!!message
val reply = future()