Scala 枚举示例和特性
枚举(Enumerations)是一种语言特性,对于建模有限的实体集来说特别有用。一个经典的例子是将工作日建模为一个枚举:每个七天都有一个值。Scala
和许多其他语言一样,提供了一种表示枚举的方法:
object Weekday extends Enumeration {
val Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday = Value
}
现在我们可以准确清晰地表示工作日,而无需使用String
或Int
等基本类型。Scala
枚举还提供了一组有用的特性:
scala> Weekday.Monday.toString
res0: String = Monday
scala> Weekday.withName("Monday")
res1: Weekday.Value = Monday
scala> Weekday.withName("Mondai")
java.util.NoSuchElementException: No value found for 'Mondai'
at scala.Enumeration.withName(Enumeration.scala:124)
... 32 elided
object Weekday extends Enumeration {
val Monday = Value("Mo.")
val Tuesday = Value("Tu.")
val Wednesday = Value("We.")
val Thursday = Value("Th.")
val Friday = Value("Fr.")
val Saturday = Value("Sa.")
val Sunday = Value("Su.")
}
scala> Weekday.Monday.toString
res0: String = Mo.
scala> Weekday.values
res0: Weekday.ValueSet = Weekday.ValueSet(Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday)
object Weekday extends Enumeration {
val Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday = Value
}
// 按照枚举值声明的顺序排序
scala> Weekday.values.toList.sorted
res0: List[Weekday.Value] = List(Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday)
object Weekday extends Enumeration {
val Monday = Value(1)
val Tuesday = Value(2)
val Wednesday = Value(3)
val Thursday = Value(4)
val Friday = Value(5)
val Saturday = Value(6)
val Sunday = Value(0)
}
// 按照枚举对应的数字值排序
scala> Weekday.values.toList.sorted
res1: List[Weekday.Value] = List(Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday)
然而,这种方法有一些问题。主要有两个缺点:
object Weekday extends Enumeration {
val Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday = Value
}
object OtherEnum extends Enumeration {
val A, B, C = Value
}
def test(enum: Weekday.Value) = {
println(s"enum: $enum")
}
def test(enum: OtherEnum.Value) = {
println(s"enum: $enum")
}
<console>:25: error: double definition:
def test(enum: Weekday.Value): Unit at line 21 and
def test(enum: OtherEnum.Value): Unit at line 25
have same type after erasure: (enum: Enumeration#Value)Unit
def test(enum: OtherEnum.Value) = {
^
def nonExhaustive(weekday: Weekday.Value) {
weekday match {
case Monday => println("I hate Mondays")
case Sunday => println("The weekend is already over? :( ")
}
}
在Scala
中,我们严重依赖于编译器强大的类型系统,使用这种方法,编译器不能找到非穷尽模式匹配子句,也不能对不同的枚举使用重载方法。
为了避免这种问题,我们可以其他办法实现枚举:
如果您决定使用sealed case objects
,Scala
编译器可以解决Scala
枚举中存在的两个问题。编译器既可以检测非穷尽模式匹配,也可以避免类型擦除问题。
sealed trait Weekday
case object Monday extends Weekday
case object Tuesday extends Weekday
case object Wednesday extends Weekday
case object Thursday extends Weekday
case object Friday extends Weekday
case object Saturday extends Weekday
case object Sunday extends Weekday
def test(weekday: Weekday) = {
weekday match {
case Monday => println("I hate Mondays")
case Sunday => println("The weekend is already over? :( ")
}
}
<console>:15: warning: match may not be exhaustive.
It would fail on the following inputs: Friday, Saturday, Thursday, Tuesday, Wednesday
weekday match {
^
test: (weekday: Weekday)Unit
另一个非常好的特性是,可以在枚举值中包含更多字段(Scala enumerations only provides an index and a name),仅仅使用sealed abstract class
而不是sealed trait
。
sealed abstract class Weekday( val name: String,
val abbreviation: String,
val isWorkDay: Boolean)
case object Monday extends Weekday("Monday", "Mo.", true)
case object Tuesday extends Weekday("Tuesday", "Tu.", true)
case object Wednesday extends Weekday("Wednesday", "We.", true)
case object Thursday extends Weekday("Thursday", "Th.", true)
case object Friday extends Weekday("Friday", "Fr.", true)
case object Saturday extends Weekday("Saturday", "Sa.", false)
case object Sunday extends Weekday("Sunday", "Su.", false)
但是这种方式也有它自己的问题:
sealed abstract class Weekday( val name: String,
val abbreviation: String,
val isWeekDay: Boolean,
val order: Int) extends Ordered[Weekday] {
def compare(that: Weekday) = this.order - that.order
}
case object Monday extends Weekday("Monday", "Mo.", true, 2)
case object Tuesday extends Weekday("Tuesday", "Tu.", true, 3)
case object Wednesday extends Weekday("Wednesday", "We.", true, 4)
case object Thursday extends Weekday("Thursday", "Th.", true, 5)
case object Friday extends Weekday("Friday", "Fr.", true, 6)
case object Saturday extends Weekday("Saturday", "Sa.", false, 7)
case object Sunday extends Weekday("Sunday", "Su.", false, 1)
scala> Monday < Tuesday
res0: Boolean = true
itemized是一个OSS lib
,它是rbrick的一部分,rbricks是一种可组合的、占用空间小的Scala
库的集合。
itemized
为枚举提供了密封特质层次结构(sealed trait hierarchies)的宏和类型类,回到我们之前的例子:
import io.rbricks.itemized.annotation.enum
@enum trait Weekday {
object Monday
object Tuesday
object Wednesday
object Thursday
object Friday
object Saturday
object Sunday
}
除上面的以外,itemized
还有其他的一些特性:
scala> import io.rbricks.itemized.ItemizedCodec
scala> ItemizedCodec[Weekday].fromRep("Monday")
res0: Option[Weekday] = Some(Monday)
scala> val weekday: Weekday = Planet.Monday
scala> import io.rbricks.itemized.ItemizedCodec.ops._
scala> weekday.toRep
res1: String = Earth
尽管itemized
可以让我们用注解方式创建类型安全的枚举,但是它也有一些不足:
Enumeratum是一个类型安全且功能强大的Scala
枚举实现,它提供了详尽的模式匹配警告。
import enumeratum._
sealed trait Weekday extends EnumEntry
object Weekday extends Enum[Weekday] {
val values = findValues // mandatory due to Enum extension
case object Monday extends Weekday
case object Tuesday extends Weekday
case object Wednesday extends Weekday
case object Thursday extends Weekday
case object Friday extends Weekday
case object Saturday extends Weekday
case object Sunday extends Weekday
}
def test(weekday: Weekday) = {
weekday match {
case Weekday.Monday => println("I hate Mondays")
case Weekday.Sunday => println("The weekend is already over? :( ")
}
}
<console>:18: warning: match may not be exhaustive.
It would fail on the following inputs: Friday, Saturday, Thursday, Tuesday, Wednesday
weekday match {
^
test: (weekday: Weekday)Unit
除了非详尽的模式匹配警告,enumeratum
还提供:
Enum
继承上实现)scala> Weekday.withName("Monday")
res0: Weekday = Monday
scala> Weekday.withName("Momday")
java.util.NoSuchElementException: Momday is not a member of Enum (Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday)
at enumeratum.Enum$$anonfun$withName$1.apply(Enum.scala:82)
at enumeratum.Enum$$anonfun$withName$1.apply(Enum.scala:82)
at scala.Option.getOrElse(Option.scala:121)
at enumeratum.Enum$class.withName(Enum.scala:81)
at Weekday$.withName(<console>:13)
... 43 elided
scala> Weekday.withNameOption("Monday")
res2: Option[Weekday] = Some(Monday)
scala> Weekday.withNameOption("Momday")
res3: Option[Weekday] = None
sealed abstract class Weekday( val name: String,
val abbreviation: String,
val isWorkDay: Boolean) extends EnumEntry
case object Weekday extends Enum[Weekday] {
val values = findValues
case object Monday extends Weekday("Monday", "Mo.", true)
case object Tuesday extends Weekday("Tuesday", "Tu.", true)
case object Wednesday extends Weekday("Wednesday", "We.", true)
case object Thursday extends Weekday("Thursday", "Th.", true)
case object Friday extends Weekday("Friday", "Fr.", true)
case object Saturday extends Weekday("Saturday", "Sa.", false)
case object Sunday extends Weekday("Sunday", "Su.", false)
}
sealed abstract class Weekday(val order: Int) extends EnumEntry with Ordered[Weekday] {
def compare(that: Weekday) = this.order - that.order
}
object Weekday extends Enum[Weekday] {
val values = findValues
case object Monday extends Weekday(2)
case object Tuesday extends Weekday(3)
case object Wednesday extends Weekday(4)
case object Thursday extends Weekday(5)
case object Friday extends Weekday(6)
case object Saturday extends Weekday(7)
case object Sunday extends Weekday(1)
}
如果您刚刚开始学习Scala
,我建议使用scala.Enumeration
的方式实现枚举。当您觉得使用更多Scala
特性更舒服时,以及开始享受编译器安全性时,可以试试其他方式实现枚举。我的两个建议是:
sealed hierarchies
enumeratum
,因为它提供了这里提到的所有特性