Scala 枚举值

2019-05-04 21:08:08 | 编辑 | 添加

虽然许多编程语言都内置了枚举值,但是Scala 却使用了另外一种实现方式:在标准库中专门定义Enumeration 类,这意味着Scala 并未提供任何特殊语法来支持枚举,而Java 则不然。所以Scala 语言中的枚举与Java 中的enum 结构在字节码层面上没有任何联系。

object Breed extends Enumeration {
  type Breed = Value
  val doberman = Value("Doberman Pinscher")
  val yorkie = Value("Yorkshire Terrier")
  val scottie = Value("Scottish Terrier")
  val dane = Value("Great Dane")
  val portie = Value("Portuguese Water Dog")
}
import Breed._
// 打印所有犬种及其ID列表
println("ID\tBreed")
for (breed <- Breed.values) println(s"${breed.id}\t$breed")
// 打印犬列表
println("\nJust Terriers:")
Breed.values filter (_.toString.endsWith("Terrier")) foreach println
def isTerrier(b: Breed) = b.toString.endsWith("Terrier")
println("\nTerriers Again??")
Breed.values filter isTerrier foreach println

该程序会打印以下信息:

ID Breed
0 Doberman Pinscher
1 Yorkshire Terrier
2 Scottish Terrier
3 Great Dane
4 Portuguese Water Dog
Just Terriers:
Yorkshire Terrier
Scottish Terrier
Terriers Again??
Yorkshire Terrier
Scottish Terrier


我们会发现犬种枚举类型中包含了许多Value 类型值,如下所示:

val doberman = Value("Doberman Pinscher")


实际上,每个犬种声明均调用了接收单一字符串参数的Value 方法。我们使用该方法为每个枚举值指定了较长的犬种名称。调用Value.toString 方法将返回该字符串。


Breed 类型是一个别名,无需使用各个Value 值,仅用Breed 枚举便能定位到具体犬种。只有在输入isTerrie 方法参数时我们才真正需要使用Value 值。假如我们注释Breed 的类型定义,那么该函数就无法通过编译。


尽管类型名和方法名均为Value,但它们之间并不存在命名空间冲突。因为编译器为值和方法分别维护了各自独立的命名空间。


Scala 还提供了一些其他的重载版Value 方法。我们之前使用的Value 方法接收单一字符串输入,而另一个Value 方法则不接受任何输入参数。无参的Value 方法将对象名作为输入字符串,例如:变量doberman 对应的字符串是doberman。第三个Value 方法的输入参数是一个整型ID 值,该方法在使用默认字符串(即变量名)的同时会将我们显式指定的整数值作为ID 值。最后一个Value 方法同时接收整数和字符串输入。


由于我们调用的方法并未显式指定ID 值,Value 对象的ID 将会自动从0 开始分配,并按照声明的顺序逐一递增。这些Value 方法都会生成Value 对象,而这些新创建的Value 对象也会被添加到枚举的Value 集合中。


通过调用了value 方法,我们能像处理集合那样处理这些枚举值。这样一来,我们便能使用for 推导式遍历所有的犬种,对这些犬种按名称进行过滤,也能查看系统为那些未指定ID 的犬种自动分配的ID 值。


就像这个示例表现的那样,我们通常希望能给枚举值取一个可读性强的名字。但是有时候你也许又不需要对枚举值命名,下面这一示例改编自Scaladoc 文档中枚举类型的入口页的示例。


object WeekDay extends Enumeration {
    type WeekDay = Value
    val Mon, Tue, Wed, Thu, Fri, Sat, Sun = Value
  }
  import WeekDay._
  def isWorkingDay(d: WeekDay) = ! (d == Sat || d == Sun)
  WeekDay.values filter isWorkingDay foreach println

  运行上述脚本将输出下列信息(v2.7):

  Mon
  Tue
  Wed
  Thu
  Fri

请注意,我们引入了WeekDay._,这使得每一个枚举值(如Mon、Tues)都在代码的作用域内。否则的话,你需要编写像WeekDay.Mon、WeekDay.Tus 这样的代码。我们同样可以通过调用values 方法遍历枚举值。在这个示例中,我们就过滤出“工作日”对应的枚举值。


尤其与Java 相比,枚举在Scala 代码中出现的次数并不多。尽管Scala 中的枚举使用便利,但是需要预先知道集合中应包含哪些枚举值。而客户是无法增加其他的枚举值的。

作为枚举的一种替代品,case 类常常应用于那些需要使用“枚举值”的场景中。尽管case类更重量级一些,但是却具有两大优势。首先,case 类允许添加方法和字段,而且我们也能够对枚举值应用模式匹配,这便为用户提供了更好的灵活性。其次case 类能适用于包含未知枚举值的场景。只要有需要,用户代码便可以将更多的case 类添加到基本集合中。