1. 概述
在本教程中,我们将深入探讨 Scala 中 case object 和 object 的概念。
我们会对比它们之间的核心差异,并讨论 case object 所具备的一些额外特性。
2. Scala Object
Scala 的 object
表示一个类只有一个实例 —— 即单例对象(Singleton)。
下面是一个 object
的例子:
object Car {
val numberOfWheels = 4
def run(): Unit = {
val currentDateAndTime: Date = new Date(System.currentTimeMillis())
println(s"I am a new car running on $currentDateAndTime!")
}
}
与类不同的是,**object
不允许在构造时传入参数**。使用 object
创建单例非常适合那些不可变的、全局唯一的实例。
3. Case Object 与 Object 的区别
这一章节我们先来看一个简单的 case object
示例,然后重点分析它和普通 object
的主要差异。
来看一个 case object
的例子:
case object Bicycle {
val numberOfWheels = 2
def run(): Unit = {
val currentDateAndTime: Date = new Date(System.currentTimeMillis())
}
}
从语法上看,唯一的不同就是前面加了关键字 case
。但功能上,✅ case object
继承了 object
的所有特性,并在此基础上扩展了一些实用功能:
- 默认实现序列化支持
- 支持模式匹配(Pattern Matching)
- 更方便地用于枚举场景
- 默认实现了
toString
方法
下面我们逐个来看这些增强功能。
3.1. 序列化支持
case object
默认实现了 Serializable
接口,而普通 object
则没有。
可以通过如下测试验证:
class ObjectExampleUnitTest extends AnyFlatSpec {
"Bicycle" should "be an instance of Serializable" in {
assert(Bicycle.isInstanceOf[Serializable])
}
"Car" should "not be an instance of Serializable" in {
assert(!Car.isInstanceOf[Serializable])
}
}
如果想让普通 object
可序列化,需要手动继承 Serializable
trait:
object Car extends Serializable {
val numberOfWheels = 4
def run(): Unit = {
val currentDateAndTime: Date = new Date(System.currentTimeMillis())
}
}
3.2. 模式匹配支持
case object
在模式匹配中表现更友好。
首先定义一个抽象类 Vehicle
,并让 Car
和 Bicycle
都继承它:
abstract class Vehicle
object Car extends Vehicle
case object Bicycle extends Vehicle
接着创建一个方法进行模式匹配:
def messageVehicle(vehicle: Vehicle): Unit = {
vehicle match {
case Car => println("send message to Car")
case Bicycle => println("send message to Bicycle")
}
}
当调用:
messageVehicle(Car)
输出结果为:
send message to Car
⚠️ 注意:虽然 object
也能参与模式匹配,但在某些场景下(如编译器检查)不如 case object
安全直观。
3.3. 枚举用途
object
和 case object
都可以用来实现枚举类型,但方式和效果略有不同。
使用 object
实现枚举
Scala 没有像 Java 那样的 enum
关键字,而是提供了 Enumeration
类来模拟枚举:
object FlyingObject extends Enumeration {
val airplane = Value("AP")
val bird = Value("BD")
val drone = Value("DE")
}
访问成员的方式包括:
println(FlyingObject.bird.id) // 输出 ID
默认情况下,ID 是按顺序递增分配的。也可以手动指定 ID:
object FlyingObjectChangingID extends Enumeration {
val airplane = Value(2, "AP")
val bird = Value(3, "BD")
val drone = Value(1, "DE")
}
此时顺序会变成 "DE", "AP", "BD"
。
❌ 但是有个问题:在做模式匹配时不会触发编译期的完整性检查。例如:
def nonExhaustive(objects: FlyingObject.Value) {
objects match {
case FlyingObject.airplane => println("I am an airplane")
case FlyingObject.bird => println("I am a bird")
}
}
nonExhaustive(FlyingObject.drone) // 运行时报错:scala.MatchError
使用 sealed case object
实现安全枚举
为了规避这个问题,推荐使用 sealed trait
+ case object
的组合:
sealed trait FlyingCaseObjects
case object AirplaneCaseObject extends FlyingCaseObjects
case object BirdCaseObject extends FlyingCaseObjects
case object DroneCaseObject extends FlyingCaseObjects
✅ 编译器能识别所有子类型,从而在模式匹配不完整时给出警告:
def sealedTraitMatch(flyingObject: FlyingCaseObjects): Unit = {
flyingObject match {
case AirplaneCaseObject => println("I am an airplane")
case BirdCaseObject => println("I am a bird")
// 缺少 DroneCaseObject 的 case,编译器会报 warning
}
}
这种方式比传统的 Enumeration
更现代、更安全。
3.4. toString 方法的默认实现
最后一个区别是 toString
方法的默认行为。
执行以下代码:
println(Car)
println(Bicycle)
输出结果如下:
com.baeldung.scala.caseobject.Car$@2f7c7260
Bicycle
✅ case object
自动重写了 toString
方法返回其名称,而普通 object
则输出的是默认哈希地址字符串。
这使得调试和日志打印更加清晰明了。
4. 总结
在这篇文章中,我们比较了 Scala 中 case object
和 object
的异同点:
- 语法上,
case object
只是多了个case
; - 功能上,
case object
增强了序列化、模式匹配、枚举支持和toString
实现; - 在实际开发中,如果需要使用到模式匹配、枚举或序列化等场景,建议优先考虑使用
case object
; - 对于只需要单例功能的对象,普通
object
已足够。
一如既往,完整代码可从 GitHub 获取。