Scala 对象

摘自《快学Scala》第六章

0.重点

需要某个类的单个实例时,或者想为其他值或函数找一个可以挂靠的地方时,就会用到object。
* 用对象作为单例或存放工具方法
* 类可以拥有一个同名的伴生对象
* 对象可以扩展类或特质
* 对象的apply方法通常用来构造伴生类的新实例
* 如果不想显式定义main方法,可以用扩展App特质的对象
* 可以通过扩展Enumeration对象来实现枚举

1.单例对象

Scala没有静态方法或静态字段,你可以用object这个语法结构来达到同样目的。
如:

object Accounts{
    private var lastNumber = 0
    def newUniqueNumber()={
        lastNumber += 1
        lastNumber
    }
}

对象的构造器在该对象第一次被使用时调用。
在本例中,Accounts的构造器在Accounts.newUniqueNumber()的首次调用时执行。如果一个对象从未被使用,那么其构造器也不会被执行。
不能提供object的构造器参数~

6.2 伴生对象

在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()来调用伴生对象的方法。

3.扩展类或trait的对象

一个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, ...)
// 打开和保存功能尚未实现

4.apply方法

我们通常会定义和使用对象的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)

5.应用程序对象

每个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方法,并且还可以根据要求打印出逝去的时间。

6.枚举

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")

你可能感兴趣的:(scala)