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


原始标题:Guide to Scala Enumerations