1. 简介
枚举(Enumeration)是一种包含一组命名常量的数据类型。在 Scala 中,Scala 2 提供了一个名为 Enumeration
的抽象类用于创建和操作枚举;而 Scala 3 则引入了新的关键字 enum
来更优雅地定义枚举。
本篇文章将带你深入理解如何扩展并自定义 Scala 2 的 Enumeration
类,并介绍 Scala 3 中更为强大的枚举语法。
2. Scala 2 中的枚举
我们以“手指”为例,来演示如何在 Scala 2 中使用 Enumeration
。
2.1. 创建一个 Enumeration
首先我们创建一个表示手指的枚举:
object Fingers extends Enumeration {
type Finger = Value
val Thumb, Index, Middle, Ring, Little = Value
}
✅ 要点解析:
Enumeration
提供了一个名为Value
的类型,用于表示每个枚举项。val Thumb, Index, ... = Value
是一种简洁的语法,用来批量创建枚举值。type Finger = Value
定义了类型别名,方便后续使用。
⚠️ 注意: 不建议在枚举创建后动态添加新值,因为其线程安全性无法保证。
2.2. 获取枚举值
我们写一个函数判断某个手指是否是最短的:
class FingersOperation {
def isShortest(finger: Finger) = finger == Little
}
单元测试验证:
@Test
def givenAFinger_whenIsShortestCalled_thenCorrectValueReturned() = {
val operation = new FingersOperation()
assertTrue(operation.isShortest(Little))
assertFalse(operation.isShortest(Index))
}
再写一个函数返回最长的两个手指:
def twoLongest() =
Fingers.values.toList.filter(finger => finger == Middle || finger == Index)
测试:
@Test
def givenFingers_whenTwoLongestCalled_thenCorrectValuesReturned() = {
val operation = new FingersOperation()
assertEquals(List(Index, Middle), operation.twoLongest())
}
2.3. 自定义 ID 和名称
每个枚举值都有一个 ID 和名称,默认从 0 开始递增,名称与变量名一致:
@Test
def givenAFinger_whenIdAndtoStringCalled_thenCorrectValueReturned() = {
assertEquals(0, Thumb.id)
assertEquals("Little", Little.toString())
}
我们可以通过 Value(id, name)
来自定义:
val Thumb = Value(1, "Thumb Finger")
val Index = Value(2, "Pointing Finger")
val Middle = Value(3, "The Middle Finger")
val Ring = Value(4, "Finger With The Ring")
val Little = Value(5, "Shorty Finger")
验证修改后的 ID 和名称:
assertEquals(1, Thumb.id)
assertEquals("Shorty Finger", Little.toString())
2.4. 通过名称反序列化枚举值
使用 withName
方法可以通过名称获取枚举值:
assertEquals(Middle, Fingers.withName("The Middle Finger"))
❌ 踩坑提示: 如果名称不存在,会抛出 java.util.NoSuchElementException
。
2.5. 修改排序
枚举值默认按照 ID 排序:
assertEquals(List(Thumb, Index, Middle, Ring, Little), Fingers.values.toList)
如果我们想让 Thumb
排在最后,只需要将它的 ID 设为最大:
val Thumb = Value(6, "Thumb Finger")
验证排序:
assertEquals(List(Index, Middle, Ring, Little, Thumb), Fingers.values.toList)
3. 为枚举添加属性
目前的枚举值只能表示“名字”和“ID”,如果我们想添加“高度”等信息,可以扩展 Val
类:
protected case class FingerDetails(i: Int, name: String, height: Double)
extends super.Val(i, name) {
def heightInCms(): Double = height * 2.54
}
添加类型转换:
import scala.language.implicitConversions
implicit def valueToFingerDetails(x: Value): FingerDetails =
x.asInstanceOf[FingerDetails]
定义每个手指的属性:
val Thumb = FingerDetails(6, "Thumb Finger", 1)
val Index = FingerDetails(2, "Pointing Finger", 4)
val Middle = FingerDetails(3, "The Middle Finger", 4.1)
val Ring = FingerDetails(4, "Finger With The Ring", 3.2)
val Little = FingerDetails(5, "Shorty Finger", 0.5)
重写函数使用新属性:
def isShortest(finger: Finger) =
Fingers.values.toList.sortBy(_.height).head == finger
def twoLongest() =
Fingers.values.toList.sortBy(_.heightInCms()).takeRight(2)
4. 枚举的问题与替代方案
4.1. Enumeration
的问题
- ❌ 类型擦除问题: 所有枚举在运行时类型相同,无法重载方法。
- ❌ 无编译期匹配检查:
match
语句不完整也不会报错,运行时会抛出MatchError
。
示例:
object Operation extends Enumeration {
type Operation = Value
val Plus, Minus = Value
}
object Conflicts {
// compile error
def getValue(f: Fingers.Finger) = f.toString
def getValue(o: Operation.Operation) = o.toString
}
4.2. 使用 Sealed Trait 替代
更好的替代方式是使用 sealed trait
+ case object
:
✅ 优点:
- 编译期可检查
match
完整性 - 更灵活,支持方法、属性定义
❌ 缺点:
- 没有内置的
values
方法 - 无法通过名称反序列化
- 需要手动实现排序逻辑
5. Scala 3 中的枚举
Scala 3 引入了全新的 enum
关键字,语法更简洁,功能更强大。
5.1. 创建枚举
enum Fingers:
case Thumb, Index, Middle, Ring, Little
访问方式:
Fingers.Thumb
5.2. 为枚举添加属性
enum Fingers(val height: Double):
case Thumb extends Fingers(1)
case Index extends Fingers(4)
case Middle extends Fingers(4.1)
case Ring extends Fingers(3.2)
case Little extends Fingers(0.5)
访问属性:
Fingers.Thumb.height // 1.0
5.3. 自定义方法
enum Fingers(val height: Double):
case Thumb extends Fingers(1)
case Index extends Fingers(4)
case Middle extends Fingers(4.1)
case Ring extends Fingers(3.2)
case Little extends Fingers(0.5)
def heightInCms(): Double = height * 2.54
调用方法:
Fingers.Thumb.heightInCms() // 2.54
6. 总结
本文对比了 Scala 2 和 Scala 3 中枚举的使用方式:
- Scala 2 使用
Enumeration
类,功能有限但稳定。 - Scala 3 引入了
enum
关键字,语法更现代,功能更强大,是未来的趋势。
示例代码已上传至 GitHub:Baeldung/scala-tutorials ✅