摘自《快学Scala》第六章
需要某个类的单个实例时,或者想为其他值或函数找一个可以挂靠的地方时,就会用到object。
* 用对象作为单例或存放工具方法
* 类可以拥有一个同名的伴生对象
* 对象可以扩展类或特质
* 对象的apply方法通常用来构造伴生类的新实例
* 如果不想显式定义main方法,可以用扩展App特质的对象
* 可以通过扩展Enumeration对象来实现枚举
Scala没有静态方法或静态字段,你可以用object这个语法结构来达到同样目的。
如:
object Accounts{
private var lastNumber = 0
def newUniqueNumber()={
lastNumber += 1
lastNumber
}
}
对象的构造器在该对象第一次被使用时调用。
在本例中,Accounts的构造器在Accounts.newUniqueNumber()
的首次调用时执行。如果一个对象从未被使用,那么其构造器也不会被执行。
不能提供object的构造器参数~
在Java或C++中,通常会遇到既有实例方法,又有静态方法的类,在Scala中可以通过与类同名的”伴生对象”来达到同样的目的。
如:
class Account{
val id = Account.newUniqueMember()
private var balance = 0.0
def deposit(amount:Double){
balance += amount
}
...
}
object Account{ //伴生对象
private var lastNumber = 0
private def newUniqueNumber() = {
lastNumber += 1
lastNumber
}
}
类和它的伴生对象可以相互访问私有特性。它们必须存在于同一个源文件中。
类的伴生对象可以被访问,但并不在作用域中。举例来说,Account类必须通过Account.newUniqueNumber()
而不是直接用newUniqueNumber()
来调用伴生对象的方法。
一个object可以扩展类以及一个或多个trait,其结果是一个扩展了指定类以及trait的类的对象,同时拥有在对象定义中给出的所有特性。
一个有用的场景是给出可被共享的缺省对象。举例来说,考虑在程序中引入一个可撤销动作的类:
abstract class UndoableAction(val description:String){
def undo():Unit
def redo():Unit
}
默认情况是“什么都不做”,当然了,对于这个行为我们只需要一个实例即可。
object DoNothingAction extends UndoableAction("Do nothing"){
override def undo(){}
override def redo(){}
}
DoNothingAction
对象可以被所有需要这个缺省行为的地方共用。
val actions = Map("open" -> DoNothingAction, "save" -> DoNothingAction, ...)
// 打开和保存功能尚未实现
我们通常会定义和使用对象的apply方法。当遇到如下形式的表达式时,apply就会被调用:
object(参数1,参数2,...,参数N)
通常,这样一个apply方法返回的是伴生类的对象。
如:Array对象定义了apply方法,让我们可以用下面这样的表达式来创建数组:
Array("Mary","had","a","little","lamb")
为什么不用构造器呢?对于嵌套表达式而言,省去new关键字会方便很多。
Array(Array(1,7),Array(2,9))
注意:
Array(100) 和 new Array(100)很容易搞混。
Array(100),输出一个单元素(整数100)的Array[Int];第二个表达式调用的是构造器this(100),结果是Array[Nothing],包含了100个null元素。
定义apply方法的示例:
class Account private (val id:Int, initialBalance:Double){
private var balance = initialBalance
...
}
object Account{ //伴生对象
def apply(initialBalance:Double) =
new Account(newUniqueNumber(), initialBalance)
...
}
这样就可以用如下代码来构造账号:
val acct = Account(1000.0)
每个Scala程序都必须从一个对象的main方法开始,这个方法的类型为Array[String]=>Unit
:
object Hello{
def main(args:Array[String]){
println("Hello world")
}
}
除了每次都提供自己的main方法外,你也可以扩展App trait,然后将程序代码放入构造器方法体内。
object Hello extends App{
println("Hello world")
}
//如果需要命令行参数:
object Hello extends App{
if(args.length > 0)
println("Hello, " + args(0))
else
println("Hello world")
}
所有这些设计一些magic。App trait扩展自另一个trait DelayedInit,编译器对该trait有特殊处理。所有带有该trait的类,其初始化方法都会被挪到delayedInit方法中。trait App 的main方法捕获到命令行参数,调用delayedInit方法,并且还可以根据要求打印出逝去的时间。
Scala没有枚举类型。不过,标准类库提供一个Enumeration助手类,可以用于产生枚举。
定义一个扩展Enumeration类的对象并以Value方法调用初始化枚举中的所有可选值。
例如:
object TrafficLightColor extends Enumeration{
val Red, Yellow, Green = Value
//相当于
// val Red = Value
// val Yellow = Value
// val Green = Value
//或者也可以向Value方法传入ID, 名称,或两个参数都传:
// val Red = Value(0,"Stop")
// val Yellow = Value(10) //相当于 val Yellow = Value(10,"Yellow")
// val Green = Value("Go") //相当于 val Green = Value(11,"Go")
}
ID在前一个枚举值基础上加1,从0开始。缺省名称为字段名。
定义完成后,就可以用TrafficLightColor.red,TrafficLightColor.Yellow, TrafficLightColor.Green来引用枚举值。
也可以:
import TrafficLightColor._
枚举值的类型是TrafficLightColor.Value
而不是TrafficLightColor
——后者是握有这些值的对象。
可以增加一个类型别名:
object TrafficLightColor extends Enumeration{
type TrafficLightColor = Value
val Red, Yellow, Green = Value
}
// 现在枚举的类型变成了TrafficLightColor.TrafficLightColor ,
//但仅当你使用import语句时,这样做才显得有意义。
import TrafficLightColor._
def doWhat(color:TrafficLightColor) = {
if(color = "red") "stop"
else if (color == Yellow) "hurry up"
else if "go"
}
枚举值的ID通过id方法返回,名称通过toString方法返回。
for (c <- TrafficLightColor.values) println(c.id + " : " + c)
// 通过枚举的ID或名称来进行查找定位:
TrafficLightColor(0) //将调用Enumeration.apply
TrafficLightColor.withName("Red")