第四章 类和对象
4.1 类,字段和方法
假设有这样的类:
class ChecksumAccumulator {
private var sum = 0
def add(b: Byte): Unit = {
sum += b
}
def checksum(): Int = {
return -(sum & 0xFF) + 1
}
}
由于函数体只有一句话,因此可以去掉外面的大括号;另外,checksum方法最后的return语句是多余的可以去掉。如果没有发现任何显式的返回语句,Scala方法将返回方法中最后一个计算得到的值。将代码简化成如下所示:
class ChecksumAccumulator {
private var sum = 0
def add(b: Byte): Unit = sum += b
def checksum(): Int = -(sum & 0xFF) + 1 }
由于add方法的结果类型为Unit,执行的目的就是它的副作用。例如add的副作用就是sum被重新赋值了。这种情况下可以去掉等号和结果类型,把方法放在大括号里,如下所示:
class ChecksumAccumulator {
private var sum = 0
def add(b: Byte) { sum += b }
def checksum(): Int = -(sum & 0xFF) + 1
}
注意,如果没写结果类型,但是写了“=”,那么编译器会根据最后返回值的类型确定函数结果类型;若是去掉了结果类型和“=”,那么函数的结果类型一定为Unit。
scala能将任何类型转换为Unit,但是转换后原来的值就丢失了。看下面的例子
//定义一个不带“=”的函数
scala> def g() {"hello world"}
//编译结果
g: ()Unit
//使用println输出g
scala> println(g)
//输出结果
()
//定义一个带“=”的函数
scala> def f() = {"hello world"}
//编译结果
f: ()String
//使用println输出f
scala> println(f)
//输出结果
hello world
4.2 分号推断
Scala程序里,语句末尾的分号通常是可选的。如果你愿意可以输入一个,但若一行里仅有一个语句也可不写。另一方面,如果一行里写多个语句那么分号是需要的
4.3 Singleton对象
scala中没有静态成员,取而代之的是单例对象Singleton Object,使用object关键字来定义。
伴生对象与伴生类:若某个单例对象和某个类共享同一个名称,那么这个单例对象称为这个类的伴生对象,这个类被称为这个对象的伴生类。类和它的伴生对象可以相互访问其私有成员。
有如下代码:
import scala.collection.mutable.Map
object ChecksumAccumulator {
private val cache = Map[String, Int]()
def calculate(s: String): Int =
if (cache.contains(s))
cache(s)
else {
val acc = new ChecksumAccumulator
for (c <- s)
acc.add(c.toByte)
val cs = acc.checksum()
cache +=(s -> cs)
cs
}
}
这个object和上一小节定义的类同名,因此为该类的伴生对象,可以访问其中的私有成员变量和方法。
该对象有一个名为cache的私有成员变量,以及一个名为calculate的方法。在calculate方法中对于传入的String类型的参数s,首先判断cache中是否存在,若存在则返回该key对应的value;若不存在,则创建一个ChecksumAccumulator对象,调用该对象的add()方法和checksum()方法,最后将结果存入到cache中。
单例对象中的方法可以使用“类名.方法名”来直接调用,而不用new一个对象。类可以带参数,但单例对象不带参数(没法通过new来实例化单例对象)。单例对象会在第一次被访问的时候初始化。
在java里类名必须和文件名相同,但在scala里却不一定。
注意:也有的object并没有共享名称的类,这类单例对象称为“孤立对象”。使用场景:把相关的功能收集在一起(如将某一类的transfer功能都放到某个object中)或定义一个scala应用的入口(如在object里写一个main()方法成为应用的入口)
4.4 Scala的Application特质
有如下代码:
import ChecksumAccumulator.calculate object FallWinterSpringSummer extends Application {
for (season <- List("fall", "winter", "spring"))
println(season +": "+ calculate(season)) }
通过继承Application,可以不用写main方法,而是直接在大括号里写方法体就,然后直接点击运行就可以被运行。
原理:Trait Application声明了带有合适的签名的main方法,并由上面的单例对象继承,使它可以像个Scala程序那样用。大括号之间的代码被收集进了单例对象的主构造器,并在类被初始化时被执行。
继承自Application比写个显式的main方法要短,不过它也有些缺点。首先,如果想访问命令行参数的话就不能用它,因为args数组不可访问。第二,因为某些JVM线程模型里的局限,如果程序是多线程的就需要显式的main方法。最后,对于某些JVM,Application中执行的对象的初始化代码并不会被优化。因此只有当你的程序相对简单和单线程情况下你才可以继承Application特质。