Scala的单例对象

Scala不能定义静态成员,而是代之定义单例对象(singleton object)。以object关键字定义。对象定义了某个类的单个实例,包含了你想要的特性:

1
2
3
4
object Accounts{
private var lastNumber = 0
def newUniqueNumber() = { lastNumber += 1; lastNumber}
}

当你在应用程序中需要一个新的唯一账号时,调用Account.newUniqueNumber()即可。对象的构造器在该对象第一次被使用时调用。

在下面几个场景下可以使用Scala单例对象:

  1. 作为存放工具函数或常量的地方
  2. 高效地共享单个不可变实例
  3. 需要使用单个实例来协调某个服务时

类和单例对象间的差别是,单例对象不带参数,而类可以。因为单例对象不是用new关键字实例化的,所以没机会传递给它实例化参数。每个单例对象都被实现为虚拟类(synthetic class)的实例,并指向静态的变量,因为它们与Java静态类有相同的初始化语义

独立对象(standalone object)

不与伴生类共享名称的单例对象称为独立对象。它可以用在很多地方,例如作为相关功能方法的工具类,或者定义Scala应用的入口点。

伴生对象(companion object)

当单例对象与某个类共享同一个名称时,它就被称为是这个类的伴生对象(companion object)。类和它的伴生对象必须定义在同一个源文件中。类被称为是这个单例对象的伴生类(companion class)。类和它的伴生对象可以互相访问其私有成员

将伴生对象作为工厂使用

我们通常将伴生对象作为工厂使用。下面是一个简单的例子,可以不需要使用’new’来创建一个实例了。

1
2
3
class Bar(foo: String)object Bar {
def apply(foo: String) = new Bar(foo)
}

继承自类和特质的单例对象

一个object可以扩展类以及一个或多个特质,其结果是一个扩展了指定类以及特质的类的对象,同时拥有在对象定义中给出的所有特性。

继承自抽象类的例子

扩展类的一个有用的使用场景是给出可被共享的缺省对象。举例来说,考虑在程序中引入一个可撤销动作的类:

1
2
3
4
5
6
7
8
9
10
11
abstract class UndoableAction(val description: Sting) {
def undo(): Unit
def redo(): Unit
}

object DoNothingAction extends UndoableAction("Do nothing") {
override def undo() {}
override def redo() {}
}

//打开和保存功能尚未实现val action = Map("open" -> DoNothingAction, "save" -> DoNothingAction, ...)123456789101112

DoNothingAction对象可以被所有需要这个缺省行为的地方共用

混入特质的例子

有时,你可以混入像debugger或logging之类的特质来构建对象帮助调试对象,这样使得构建的对象实例具有log之类的方法:

1
2
3
4
5
6
7
8
9
trait Debugger {
def log(message: String){
//do something with message
}
}

//no debuggerval child = new Child

//debugger added as the object is createdval problemChild = new ProblemChild with Debugger

伴生对象小结

  1. Scala中伴生对象采用object关键字声明,伴生对象中声明的全是“静态”内容,可以通过伴生对象名称直接调用
  2. 伴生对象对应的类称之为伴生类,伴生对象的名称应该和伴生类名一致
  3. 伴生对象中的属性和方法都可以通过伴生对象名(类名)直接调用访问
  4. 从语法角度来讲,所谓的伴生对象其实就是类的静态方法和成员的集合
  5. 从技术角度来讲,scala还是没有生成静态的内容,只不过是将伴生对象生成了一个新的类,实现属性和方法的调用
  6. 从底层原理看,伴生对象实现静态特性是依赖于public static finalMODULES 实现的