七、Scala 包、样例类与样例对象

随着我们构建的系统越来越复杂,如何清晰地组织代码和高效地对数据进行建模,变得至关重要。本节,我们将深入学习 Scala 提供的两大利器:用于构建命名空间和模块化的 ,以及为数据而生、并为模式匹配提供强大支持的样例类样例对象

思维导图


一、包

1.1 包简介

包是 Scala 组织代码的基本单位。它提供了一个命名空间,可以有效避免不同代码模块间的命名冲突,并帮助我们构建清晰的项目结构。

1.2 包的定义格式

Scala 支持两种定义包的方式:

方式一:文件顶部声明

这是最常见的方式,与 Java 类似。

scala 复制代码
package com.mycompany.project.moduleA

class MyClass {
  // ...
}

方式二:嵌套风格

允许在同一个文件中定义属于不同包的内容,并共享父包的作用域。

scala 复制代码
package com.mycompany.project {
  // 这里的代码属于 com.mycompany.project 包
  
  package moduleB {
    // 这里的代码属于 com.mycompany.project.moduleB 包
    class AnotherClass {
      // ...
    }
  }
}

1.3 作用域

使用嵌套风格时,子包可以直接访问父包中的成员,无需导入。

scala 复制代码
package parent {
  class ParentClass
  
  package child {
    class ChildClass {
      val p = new ParentClass //可以直接访问
    }
  }
}

1.4 包的引入

使用 import 关键字可以引入其他包中的类、对象或成员,以便在当前文件中直接使用。

引入方式 语法 示例与说明
引入单个成员 import path.to.Member import java.util.Date
引入多个成员 import path.to.{Member1, Member2} import scala.collection.mutable.{Map, Set}
通配符引入 import path.to._ import scala.collection.mutable._ (引入 mutable 包下所有成员)
重命名引入 import path.to.{Member => NewName} import java.util.{Date => JDate} (避免与自定义的Date类冲突)
隐藏成员引入 import path.to.{Member => _, _} import java.util.{Date => _, _} (引入 util 包下除 Date 外的所有成员)

1.5 包对象

有时,我们希望在整个包的范围内共享一些常量或工具方法。包对象 就是为此而生。每个包最多只能有一个包对象。

语法:

在与包同级的目录下,创建一个名为 package.scala 的文件。

scala 复制代码
// 文件名:com/mycompany/project/package.scala
package com.mycompany

package object project {
  val PI = 3.14159
  def sayPackageHello(): Unit = println("Hello from the project package!")
}

使用:

scala 复制代码
// 在 com.mycompany.project 包下的任何文件中
package com.mycompany.project

class MyClass {
  def calculate(radius: Double): Double = {
    sayPackageHello() // 直接调用
    2 * PI * radius   // 直接使用
  }
}

1.6 包的可见性

可以使用 private[包名] 修饰符来扩大私有成员的可见范围,使其在指定的包内可见。

二、样例类

样例类是一种特殊的类,它专门为存储不可变 (immutable) 的数据而优化。当你定义一个类为 case class 时,Scala 编译器会自动为你生成一系列实用方法。

2.1 格式与特性

特性 (Feature) 编译器自动生成的内容 说明
不可变性 主构造器参数默认为 val 鼓励使用不可变的数据模型,更安全。
工厂方法 伴生对象中自动创建 apply 方法。 创建实例时无需使用 new 关键字,代码更简洁。
模式匹配支持 伴生对象中自动创建 unapply 方法。 这是样例类最重要的特性,使其能无缝用于模式匹配。
自动方法实现 实现了合理的 toString, equals, hashCode 方法。 便于打印、比较和在集合中使用。
copy 方法 自动创建 copy 方法。 可以方便地创建一个新实例,并修改其中部分字段。

2.2 示例

scala 复制代码
// 定义一个样例类
case class Person(name: String, age: Int)

// 1. 无需 new 关键字创建实例 (apply 方法)
val alice = Person("Alice", 30)
val bob = Person("Bob", 32)
val anotherAlice = Person("Alice", 30)

// 2. 友好的 toString 输出
println(alice) // 输出: Person(Alice,30)

// 3. 基于值的 equals 和 hashCode
println(alice == bob) // 输出: false
println(alice == anotherAlice) // 输出: true

// 4. 使用 copy 方法创建新实例
val olderAlice = alice.copy(age = 31)
println(olderAlice) // 输出: Person(Alice,31)

2.3 样例类中的默认方法

如上表所示,apply, unapply, toString, equals, hashCode, copy 等都是编译器为样例类自动生成的"默认方法"。

三、样例对象

3.1 格式
case object 是一个同时具备样例类特性和单例对象特性的特殊对象。

3.2 示例

样例对象非常适合用于表示枚举或固定状态的集合,通常与 sealed trait (密封特质) 配合使用。

scala 复制代码
// 密封特质,表示其所有子类必须在同一个文件中定义
sealed trait DayOfWeek

case object Monday extends DayOfWeek
case object Tuesday extends DayOfWeek
// ... 其他天

def activity(day: DayOfWeek): String = day match {
  case Monday => "Work hard"
  case _ => "Relax" // _ 是通配符
}

println(activity(Monday)) // 输出: Work hard

四、综合案例

这个案例将综合运用本节的知识,特别是样例类和样例对象,来构建一个灵活的消息模型。

8.1 需求

我们需要一个系统来表示不同类型的消息,例如文本消息、图片消息,以及一个特殊的断开连接信号。

8.2 目的

使用 sealed trait, case classcase object 来清晰地对这些消息进行建模,并编写一个可以处理不同消息类型的函数。

8.3 参考代码

scala 复制代码
package com.example.messaging

// 1. 使用 sealed trait 定义消息的基类型
sealed trait Message

// 2. 使用 case class 定义包含数据的消息类型
case class TextMessage(sender: String, content: String) extends Message
case class ImageMessage(sender: String, imageUrl: String, caption: String) extends Message

// 3. 使用 case object 定义不包含数据的信号类型消息
case object Disconnect extends Message

// 4. 创建一个消息处理器对象
object MessageProcessor {
  // 这个方法使用模式匹配来处理不同类型的消息
  // (模式匹配是后续章节的核心内容,这里是预演)
  def process(msg: Message): String = msg match {
    case TextMessage(sender, content) =>
      s"Received text from $sender: '$content'"
    case ImageMessage(sender, url, caption) =>
      s"Received an image from $sender with caption '$caption'. URL: $url"
    case Disconnect =>
      "A client has disconnected."
  }
}

// 5. 在主程序中使用
object MainApp extends App {
  import com.example.messaging._

  val text = TextMessage("Alice", "Hello there!")
  val image = ImageMessage("Bob", "http://example.com/img.png", "My vacation photo")
  val disconnectSignal = Disconnect

  println(MessageProcessor.process(text))
  println(MessageProcessor.process(image))
  println(MessageProcessor.process(disconnectSignal))
}

练习题

题目一:包定义

在一个名为 MyApp.scala 的文件中,使用嵌套风格将 DatabaseService 类定义在 com.myapp.services 包下,将 User 类定义在 com.myapp.models 包下。

题目二:包引入

在一个文件中,如何只引入 scala.collection.mutable 包中的 ArrayBufferListBuffer

题目三:重命名引入

你的代码中已经有一个 Map 类,现在你需要使用 scala.collection.immutable.Map。请写出引入语句,将 scala.collection.immutable.Map 重命名为 ImmutableMap 以避免冲突。

题目四:包对象

com.utils 包中创建一个包对象,定义一个名为 APP_VERSION 的常量,值为 "1.0.0"

题目五:简单样例类

定义一个 case class Point,它有两个 Double 类型的字段:xy

题目六:样例类实例化

使用上题定义的 Point 样例类,创建一个实例 p1,表示坐标 (3.0, 4.0),不要使用 new 关键字。

题目七:样例类的 copy 方法

基于上题创建的 p1 实例,使用 copy 方法创建一个新实例 p2,其 x 坐标与 p1 相同,但 y 坐标为 -4.0

题目八:样例类的 equals

创建两个 Point 实例,p3p4,它们都表示坐标 (1.5, 2.5)。然后判断 p3 == p4 的结果是什么。

题目九:样例对象

使用 sealed traitcase object 定义一个 Status 枚举,包含三个状态:Pending, Success, Failure

题目十:在样例类中使用 var

定义一个 case class Task,它有一个不可变的 id (Int) 和一个可变的 status (String)。

题目十一:包可见性

com.security 包中定义一个 Credentials 类。该类有一个 password 字段,使其只能在 com.security 包内部可见。

题目十二:unapply 方法的作用

样例类自动生成的 unapply 方法最主要的用途是什么?

题目十三:样例对象与样例类的区别
case classcase object 最根本的区别是什么?

题目十四:综合 - HTTP请求模型

使用 sealed trait 和样例类/对象为简单的HTTP请求方法进行建模,包括 GET(url: String), POST(url: String, body: String) 和一个表示 OPTIONS 请求的样例对象。

题目十五:综合 - 几何图形模型

定义一个 sealed trait GeometricShape。然后定义两个 case classCircle(radius: Double)Rectangle(width: Double, height: Double),都继承自 GeometricShape

答案与解析

答案一:

scala 复制代码
package com.myapp {
  package models {
    class User
  }
  package services {
    class DatabaseService
  }
}

解析: 嵌套包定义允许在同一文件中组织多个相关包的类。

答案二:

scala 复制代码
import scala.collection.mutable.{ArrayBuffer, ListBuffer}

解析: 使用花括号 {...} 可以从同一个父包中选择性地引入多个成员。

答案三:

scala 复制代码
import scala.collection.immutable.{Map => ImmutableMap}

解析: => 符号用于在引入时为成员指定一个新的名称。

答案四:

scala 复制代码
// 文件路径: com/utils/package.scala
package com

package object utils {
  val APP_VERSION = "1.0.0"
}

解析: 包对象必须定义在名为 package.scala 的文件中。

答案五:

scala 复制代码
case class Point(x: Double, y: Double)

解析: 这是定义样例类的最简洁形式。xy 默认是 public val

答案六:

scala 复制代码
val p1 = Point(3.0, 4.0)

解析: 样例类会自动生成伴生对象和 apply 方法,因此可以省略 new

答案七:

scala 复制代码
val p1 = Point(3.0, 4.0)
val p2 = p1.copy(y = -4.0)

解析: copy 方法通过带名参数可以方便地创建修改了部分字段的新实例。

答案八:

结果是 true

scala 复制代码
val p3 = Point(1.5, 2.5)
val p4 = Point(1.5, 2.5)
println(p3 == p4) // 输出: true

解析: 样例类的 equals 方法是基于值的比较,而不是引用比较。只要所有字段的值都相等,实例就相等。

答案九:

scala 复制代码
sealed trait Status
case object Pending extends Status
case object Success extends Status
case object Failure extends Status

解析: sealed trait + case object 是在 Scala 2 中实现类型安全枚举的标准模式。

答案十:

scala 复制代码
case class Task(val id: Int, var status: String)

解析: 可以在样例类的构造器参数前显式使用 var 来创建可变字段。

答案十一:

scala 复制代码
package com.security {
  class Credentials {
    private[security] var password: String = _
  }
  class Authenticator {
    def check(c: Credentials): Unit = {
      println(c.password) // 在 com.security 包内可以访问
    }
  }
}

解析: private[security]password 的可见性限定在 com.security 包内。

答案十二:
unapply 方法最主要的用途是支持模式匹配 。

解析: 它允许样例类实例被"解构",即在 case 语句中提取出其构造器参数。

答案十三:

最根本的区别是:case class 是一个类,用于创建多个实例;而 case object 是一个单例对象,全局只有一个实例。

答案十四:

scala 复制代码
sealed trait HttpMethod
case class GET(url: String) extends HttpMethod
case class POST(url: String, body: String) extends HttpMethod
case object OPTIONS extends HttpMethod

解析: 这个模型清晰地区分了需要携带数据的请求 (GET, POST) 和不需要携带数据的信号类请求 (OPTIONS)。

答案十五:

scala 复制代码
sealed trait GeometricShape
case class Circle(radius: Double) extends GeometricShape
case class Rectangle(width: Double, height: Double) extends GeometricShape

解析: sealed traitcase class 结合是构建代数数据类型 (ADT) 的基础,在函数式编程中非常常见。

日期:2025年9月21日

专栏:Scala教程

我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=480jfj75dvu

相关推荐
浩浩kids2 小时前
Scala • basis
java·开发语言·scala
泽虞3 小时前
《C++程序设计》笔记p4
linux·开发语言·c++·笔记·算法
Dream achiever3 小时前
7.WPF 的 TextBox 和 TextBlock 控件
开发语言·c#·wpf
love530love3 小时前
EPGF 架构为什么能保持长效和稳定?
运维·开发语言·人工智能·windows·python·架构·系统架构
Hello.Reader3 小时前
Flink Operations Playground 部署、观测、容错、升级与弹性扩缩
大数据·flink
l1t3 小时前
测试duckdb的C插件模板的编译加工和加载
c语言·开发语言·数据库·插件·duckdb
峥嵘life3 小时前
Android16 应用代码新特性
java·开发语言·学习·安全
浮尘笔记3 小时前
Go-Zero API Handler 自动化生成与参数验证集成
开发语言·后端·golang
傻啦嘿哟3 小时前
用Requests+BeautifulSoup实现天气预报数据采集:从入门到实战
开发语言·chrome·python