《快学Scala》——对象、包

2019-03-16

单例对象

Scala没有静态方法静态字段,可以用object来达到同样目的。

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

对象本质上可以拥有类的所有特性,只有一个例外:不能提供构造器参数。

伴生对象

在Scala中,可以通过定义和类同名的(伴生)对象来提供其他语言(例如Java)中既有实例方法又有静态方法的类。类和它的伴生类可以互相访问私有特性,但必须定义在同一个源文件中。

class Accounts {
    val id = Account.newUniqueNumber()
    private var blance = 0.0
    def deposit(amount: Double) {
      blance += amount
    }
    ...
}
object Accounts {  // 伴生对象
    private var lastNumber = 0
    def newUniqueNumber() = { lastNumber += 1; lastNumber }
}

注: object可以扩展类以及一个或多个特质,结果是一个扩展了指定类以及特质的类的对象。

我们通常会定义和使用对象的apply方法。当遇到如下形式的表达式时,apply方法就会被调用:

Object(参数1,参数2,...,参数n)

通常,这样一个apply方法返回的是伴生类的对象。示例如下:

class Account private (val id: Int, initialBalance: Double) {
    private var balance = initialBalance
    ...
}

object Account {  // 伴生对象
    def apply(initialBalance: Double) =
        new Account(newUniqueNumber(), initialBalance)
}

枚举

Scala并没有枚举类型,但是可以通过标准类库的Enumeration助手类,产生枚举。定义一个扩展Enumeration类的对象并以Value方法调用初始化枚举中的所有可选值。

object TrafficLightColor extends Enumeration {
    val Red = Value(0, "Stop")
    val Yellow = Value(10)  // 缺省名称为字段名
    val Green = Value("Go") // ID不指定时, 将在前一个枚举值基础上加一, 从零开始
    // 也可以简写为: val Red, Yellow, Green = Value
}

上例中,枚举的类型是TrafficLightColor.Value而不是TrafficLightColor。枚举值的ID可以通过id方法返回,名称可以通过toString方法返回。

// values方法获得所有枚举值得集
for (c <- TrafficLightColor.values) println(c.id + ": " + c)
// 通过枚举的ID或名称来进行查找定位
println(TrafficLightColor(2))
println(TrafficLightColor.withName("Red"))

同样的类名出现在不同的包中是不冲突的。在使用这样的类的时候可以使用完全限定的名称(包名.类名),也可以使用引入语句(import)。Scala的包和其他作用域一样地支持嵌套。通过下面形式的定义,可以访问上层作用域中的名称。

package com {
    package horstmann {
        object Utils {
          def percentOf(value: Double, rate: Double) = value * rate /100
          ...
        }
        package impatient {
            class Employee {
              ....
              def giveRaise(rate: scala.Double) {
                salary += Utils.percentOf(salary, rate)  // 因为Utils类定义在父包中,所有父包中的内容都是在作用域内的,因此没必要在前面加包名
              }
            }
        }
    }
}

串联式包语句,限定了可见的成员。

package com.horstmann.impatient {
    // com和com.horstmann的成员在这里不可见
    package people {
        class Person
        ...
    }
}

文件顶部标记法,可以在文件顶部使用package语句,不带花括号。这样做的话,表示文件中的所有代码均属于同一个包下。

package com.horstmann.impatient 
 // com和com.horstmann的成员在这里不可见
package people 
class Person {
  ...
}

包对象

可以将工具类或者常量添加到包对象中,用package object定义包对象:

package com.horstmann.impatient

package object people {
    val defaultName = "test"
}

package people {
    class Person {
        var name = defaultName // 从包对象拿到的常量
        ...
    }
}

注: defaultName不需要加限定词,因为它位于同一个包内。在其他地方,这个常量可以用com.horstmann.impatient.people.defaultName

包可见性

在Java中,没有被声明为publicprivateprotected的类成员在包含该类的包中可见。在Scala中,可以通过修饰符达到同样的效果。

package com.horstmann.impatient.people
class Person {
    // 在包impatient中可见
    private[impatient] def desc = "A person with name " + name
}

引入

import java.awt.Color
// 引入包下所有成员
import java.awt._
// 引入类或对象的所有成员
import java.awt.Color._

在Scala中,import语句可以出现在任何地方,并不局限在文件顶部。import语句的效果一直延伸到包含该语句的代码块的末尾。

import java.awt.{Color, Font}  // 引入包中多个成员时,可以使用选取器(selector)
import java.util.{HashMap => JavaHashMap}  // 还可以重命名选到的成员

// 选取器HashMap => _将隐藏某个成员,这样HashMap无二义地指向scala.collection.mutable.HashMap
import java.util.{HashMap => _, _}  
import scala.collection.mutable._

每个scala程序都隐式地以如下代码开始,这里的引入,允许覆盖之前的引入(例如scala.StringBuilder会覆盖java.lang.StringBuilder而不是与之冲突)。由于scala包默认被引入,对于那些以scala开头的包,可以省略scala。

import java.lang._
import scala._
import Predef._