七 高阶函数
7.1 作为参数的函数
函数作为一个变量传入到了另一个函数中,那么该作为参数的函数的类型是:function1,即:(参数类型) => 返回类型
def plus(x: Int) = 3 + x val result1 = Array(1, 2, 3, 4).map(plus(_)) println(result1.mkString(",")) |
尖叫提示:带有一个参数的函数的类型是function1,带有两个是function2,以此类推
7.2 匿名函数
即没有名字的函数,可以通过函数表达式来设置匿名函数。
val triple = (x: Double) => 3 * x println(triple(3)) |
7.3 高阶函数
能够接受函数作为参数的函数,叫做高阶函数。
1) 高阶函数的使用
def highOrderFunction1(f: Double => Double) = f(10) def minus7(x: Double) = x - 7 val result2 = highOrderFunction1(minus7) println(result2) |
2) 高阶函数同样可以返回函数类型
def minusxy(x: Int) = (y: Int) => x - y val result3 = minusxy(3)(5) println(result3) |
7.4 参数(类型)推断
// 传入函数表达式 highOrderFunction1((x: Double) => 3 * x) // 参数推断省去类型信息 highOrderFunction1((x) => 3 * x) // 单个参数可以省去括号 highOrderFunction1(x => 3 * x) // 如果变量旨在=>右边只出现一次,可以用_来代替 highOrderFunction1(3 * _) |
7.5 闭包
闭包就是一个函数把外部的那些不属于自己的对象也包含(闭合)进来。
def minusxy(x: Int) = (y: Int) => x - y |
这就是一个闭包:
1) 匿名函数(y: Int) => x -y嵌套在minusxy函数中。
2) 匿名函数(y: Int) => x -y使用了该匿名函数之外的变量x
3) 函数minusxy返回了引用了局部变量的匿名函数
再举一例:
def minusxy(x: Int) = (y: Int) => x - y val f1 = minusxy(10) val f2 = minusxy(10) println(f1(3) + f2(3)) |
此处f1,f2这两个函数就叫闭包。
7.6 柯里化
函数编程中,接受多个参数的函数都可以转化为接受单个参数的函数,这个转化过程就叫柯里化,柯里化就是证明了函数只需要一个参数而已。其实我们刚才的学习过程中,已经涉及到了柯里化操作,所以这也印证了,柯里化就是以函数为主体这种思想发展的必然产生的结果。
1) 柯里化示例
def mul(x: Int, y: Int) = x * y println(mul(10, 10))
def mulCurry(x: Int) = (y: Int) => x * y println(mulCurry(10)(9))
def mulCurry2(x: Int)(y:Int) = x * y println(mulCurry2(10)(8)) |
2) 柯里化的应用
比较两个字符串在忽略大小写的情况下是否相等,注意,这里是两个任务:
1、全部转大写(或小写)
2、比较是否相等
针对这两个操作,我们用一个函数去处理的思想,其实无意间也变成了两个函数处理的思想。示例如下:
val a = Array("Hello", "World") val b = Array("hello", "world") println(a.corresponds(b)(_.equalsIgnoreCase(_))) |
其中corresponds函数的源码如下:
def corresponds[B](that: GenSeq[B])(p: (A,B) => Boolean): Boolean = { val i = this.iterator val j = that.iterator while (i.hasNext && j.hasNext) if (!p(i.next(), j.next())) return false
!i.hasNext && !j.hasNext } |
尖叫提示:不要设立柯里化存在的意义这样的命题,柯里化,是面向函数思想的必然产生结果。
7.7 控制抽象
控制抽象是一类函数:
1、参数是函数。
2、函数参数没有输入值也没有返回值。
1) 使用示例
def runInThread(f1: () => Unit): Unit = { new Thread { override def run(): Unit = { f1() } }.start() }
runInThread { () => println("干活咯!") Thread.sleep(5000) println("干完咯!") } |
是不是很爽?是不是有点类似线程池的感觉,同一个线程,可以动态的向里面塞不同的任务去执行。
可以再简化一下,省略(),看下如下形式:
def runInThread(f1: => Unit): Unit = { new Thread { override def run(): Unit = { f1 } }.start() }
runInThread { println("干活咯!") Thread.sleep(5000) println("干完咯!") } |
2) 进阶用法:实现类似while的until函数
def until(condition: => Boolean)(block: => Unit) { if (!condition) { block until(condition)(block) } }
var x = 10 until(x == 0) { x -= 1 println(x) } |
八 类
7.1 简单类和无参方法
类的定义可以通过class关键字实现,如下:
class Dog { private var leg = 4 def shout(content: String) { println(content) } def currentLeg = leg } |
使用这个类:
val dog = new Dog dog shout ("汪汪汪") println(dog currentLeg) |
尖叫提示:在Scala中,类并不声明为Public,一个Scala源文件可以包含多个类。所有这些类都具有公有可见性。调用无参方法时,可以加(),也可以不加;如果方法定义中不带括号,那么调用时就不能带括号。
7.2 Getter Setter方法
对于scala类中的每一个属性,编译后,会有一个私有的字段和相应的getter、setter方法生成:
//getter println(dog leg) //setter dog.leg=(10) println(dog currentLeg) |
当然了,你也可以不使用自动生成的方式,自己定义getter和setter方法
class Dog2 { private var _leg = 4 def leg = _leg def leg_=(newLeg: Int) { _leg = newLeg } } |
使用之:
val dog2 = new Dog2 dog2.leg_=(10) println(dog2.leg) |
尖叫提示:自己手动创建变量的getter和setter方法需要遵循以下原则:
1) 字段属性名以“_”作为前缀,如:_leg
2) getter方法定义为:def leg = _leg
3) setter方法定义时,方法名为属性名去掉前缀,并加上后缀,后缀是:“leg_=”,如例子所示
7.3 对象私有字段
变量:workDetails在封闭包professional中的任何类中可访问。
封闭包:friends的任何类都可以被society包中任何类访问。
变量:secrets只能在实例方法的隐式对象(this)中访问。
package society { package professional { class Executive { private[professional] var workDetails = null private[society] var friends = null private[this] var secrets = null
def help(another: Executive) { println(another.workDetails) // println(another.secrets) 报错:访问不到 } } } } |
7.4 Bean属性
JavaBeans规范定义了Java的属性是像getXXX()和setXXX()的方法。许多Java工具都依赖这个命名习惯。为了Java的互操作性。将Scala字段加@BeanProperty时,这样的方法会自动生成。
1) 创建一个Bean,使用@BeanProperty注解标识某个属性变量
import scala.beans.BeanProperty class Person { @BeanProperty var name: String = _ } |
2) 通过getName、setName访问属性
val person = new Person person.setName("Nick") person.getName println(person.name) |
尖叫提示:
Person将会生成四个方法:
--name:String
--name_=(newValue:String): Unit
--getName():String
--setName(newValue:String):Unit
7.5 构造器
scala中构造分为主构造器和辅助构造器
1) 主构造的参数直接放置于类名之后
//定义类: class ClassConstructor (var name: String, private var price: Double){ def myPrintln = println(name + "," + price) } //执行: val classConstructor = new ClassConstructor("《傲慢与偏见》", 20.5) classConstructor.myPrintln |
2) 主构造器会执行类定义中的所有语句
//定义类: class ClassConstructor2(val name: String = "", val price: Double = 0) { println(name + "," + price) } //执行: val classConstructor2 = new ClassConstructor2("aa", 20) val classConstructor2_2 = new ClassConstructor2() |
3) 通过private设置的主构造器的私有属性
参考1) |
4) 如果不带val和var的参数至少被一个方法使用,该参数将自动升级为字段,这时,name和price就变成了类的不可变字段,而且这两个字段是对象私有的,这类似于 private[this] val 字段的效果。
否则,该参数将不被保存为字段,即实例化该对象时传入的参数值,不会被保留在实例化后的对象之中。
主构造器参数 | 生成的字段/方法 |
name: String | 对象私有字段。如果没有方法使用name, 则没有该字段 |
private val/var name: String | 私有字段,私有的getter和setter方法 |
var name: String | 私有字段,公有的getter和setter方法 |
@BeanProperty val/var name: String | 私有字段,公有的Scala版和Java版的getter和setter方法 |
如果想让主构造器变成私有的,可以在()之前加上private,这样用户只能通过辅助构造器来构造对象了
class Person private () {...} |
5) 辅助构造器名称为this,通过不同参数进行区分,每一个辅助构造器都必须以主构造器或者已经定义的辅助构造器的调用开始
class Person { 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 }
def description = name + " is " + age + " years old" } |
7.6 嵌套类
即,在class中,再定义一个class,以此类推。
Java中的内部类从属于外部类。Scala中内部类从属于实例。
1)
创建一个嵌套类,模拟局域网的聊天场景
import scala.collection.mutable.ArrayBuffer //嵌套类 class Network { class Member(val name: String) { val contacts = new ArrayBuffer[Member] } private val members = new ArrayBuffer[Member] def join(name: String) = { val m = new Member(name) members += m m } } |
2) 使用该嵌套类
//创建两个局域网 val chatter1 = new Network val chatter2 = new Network
//Fred 和 Wilma加入局域网1 val fred = chatter1.join("Fred") val wilma = chatter1.join("Wilma") //Barney加入局域网2 val barney = chatter2.join("Barney")
//Fred将同属于局域网1中的Wilma添加为联系人 fred.contacts += wilma //fred.contacts += barney //这样做是不行的,Fred和Barney不属于同一个局域网,即,Fred和Barney不是同一个class Member实例化出来的对象 |
在Scala中,每个实例都有它自己的Member类,就和他们有自己的members字段一样。也就是说,chatter1.Member和chatter2.Member是不同的两个类。也就是所谓的:路径依赖类型,此处需要详细解释之。
如果想让members接受所有实例的Member,一般有两种办法:
1) 将Member作为Network的伴生对象存在
创建类:
import scala.collection.mutable.ArrayBuffer
// 伴生对象 class Network2 { private val members = new ArrayBuffer[Network2.Member] def join(name: String) = { val m = new Network2.Member(name) members += m m } def description = "该局域网中的联系人:" + (for (m <- members) yield m.description).mkString(", ") }
object Network2 { class Member(val name: String) { val contacts = new ArrayBuffer[Member] def description = name + "的联系人:" + (for (c <- contacts) yield c.name).mkString(" ") } } |
使用:
val chatter3 = new Network2 val chatter4 = new Network2
//Fred 和 Wilma加入局域网1 val fred2 = chatter3.join("Fred") val wilma2 = chatter3.join("Wilma") //Barney加入局域网2 val barney2 = chatter4.join("Barney") //Fred将同属于局域网3中的Wilma添加为联系人 fred2.contacts += wilma2 //Fred将不同属于局域网3中,属于局域网4中的的Wilma添加为联系人 fred2.contacts += barney2
println(chatter3.description) println(chatter4.description)
println(fred2.description) println(wilma2.description) println(barney2.description) |
2) 使用类型投影,注意留意关键符号:“#”
创建类:
import scala.collection.mutable.ArrayBuffer
//投影 class Network3 { class Member(val name: String) { val contacts = new ArrayBuffer[Network3#Member] }
private val members = new ArrayBuffer[Member]
def join(name: String) = { val m = new Member(name) members += m m } } |
使用:
val chatter5 = new Network3 val chatter6 = new Network3
//Fred 和 Wilma加入局域网1 val fred3 = chatter5.join("Fred") val wilma3 = chatter5.join("Wilma") //Barney加入局域网2 val barney3 = chatter6.join("Barney") fred3.contacts += wilma3 |
尖叫提示:与Java一样,在嵌套类中,如果想得到外部类的实例化对象的引用,可以使用“外部类.this”的方式得到。
九 对象
9.1 单例对象
Scala中没有静态方法和静态字段,可以用object这个语法结构来达到同样的目的。
object Dog { println("已初始化...") private var leg = 0 def plus() = { leg += 1 leg } } |
对象的构造器在该对象第一次使用时调用。如果对象没有使用过,他的构造器也不会被执行。
对象基本具有类的所有特性,就是一点,你不能设置构造器的参数。
9.2 伴生对象
Java中的类可以既有实例方法又有静态方法,Scala中可以通过伴生对象进行实现。如下:
class Cat { val hair = Cat.growHair private var name = "" def changeName(name: String) = { this.name = name } def describe = println("hair:" + hair + "name:" + name) }
object Cat { private var hair = 0
private def growHair = { hair += 1 hair } } |
测试:
val cat1 = new Cat val cat2 = new Cat
cat1.changeName("黑猫") cat2.changeName("白猫")
cat1.describe cat2.describe |
尖叫提示:类和它的伴生对象可以相互访问私有特性,他们必须存在同一个源文件中。必须同名
9.3 Apply方法
1) apply方法一般都声明在伴生类对象中,可以用来实例化伴生类的对象:
class Man private(val sex: String, name: String) { def describe = { println("Sex:" + sex + "name:" + name) } }
object Man { def apply(name: String) = { new Man("男", name) } } |
测试:
val man1 = Man("Nick") val man2 = Man("Thomas") man1.describe man2.describe |
2) 也可以用来实现单例模式,我们只需要对上述列子稍加改进:
class Man private(val sex: String, name: String) { def describe = { println("Sex:" + sex + "name:" + name) } }
object Man { var instance: Man = null def apply(name: String) = { if(instance == null) { instance = new Man("男", name) } instance } } |
测试:
val man1 = Man("Nick") val man2 = Man("Thomas") man1.describe man2.describe |
9.4 应用程序对象
每一个Scala应用程序都需要从一个对象的main方法开始执行,这个方法的类型为Array[String]=>Unit:
object Hello { def main(args: Array[String]) { println("Hello, World!") } } |
或者扩展一个App特质:
object Hello extends App { if (args.length > 0) println("Hello, " + args(0)) else println("Hello, World!") } |
9.5 枚举
Scala中没有枚举类型,定义一个扩展Enumeration类的对象,并以value调用初始化枚举中的所有可能值:
object TrafficLightColor extends Enumeration { val Red = Value(0, "Stop") val Yellow = Value(1, "Slow") val Green = Value(2, "Go") } |
测试:
println(TrafficLightColor.Red) println(TrafficLightColor.Red.id)
println(TrafficLightColor.Yellow) println(TrafficLightColor.Yellow.id)
println(TrafficLightColor.Green) println(TrafficLightColor.Green.id) |