文章目录

Scala 伴生对象:companion object

发布于 2026-04-09 03:13:10 · 浏览 4 次 · 评论 0 条

Scala 伴生对象:companion object

Scala 语言中没有 Java 那样的 static(静态)关键字。为了实现类似于 Java 中静态成员(类变量、类方法)的功能,Scala 引入了 object 关键字。当 objectclass 在同一个源文件中且名称相同时,它就被称为“伴生对象”。伴生对象与其对应的类可以互相访问私有成员,是 Scala 中管理静态逻辑、工厂方法和单例模式的核心工具。

1. 定义伴生对象基础结构

创建一个名为 Person 的 Scala 源文件(Person.scala)。在文件中编写同名的类(class)和对象(object)。这两个定义必须在同一个文件中,才能构成伴生关系。

打开你的 IDE 或文本编辑器,输入以下代码:

class Person(val name: String, val age: Int) {
  // 实例成员
  def introduce(): String = s"Hello, I am $name."
}

// 伴生对象
object Person {
  // 静态成员
  val defaultAge = 18
  
  def getSpecies(): String = "Homo Sapiens"
}
```

在上述代码中,`Person` 类负责定义实例的行为,而 `Person` 对象则负责定义类的行为(相当于 Java 中的静态方法)。

---

### 2. 访问伴生对象的静态成员

**调用**伴生对象中的方法或字段时,直接使用**对象名**进行引用,无需创建类的实例。这与 Java 中调用 `StaticMethod()` 的方式完全一致。

**执行**以下调用代码:

```scala
// 直接通过伴生对象名访问
val species = Person.getSpecies()
val age = Person.defaultAge

// 输出结果
println(species) // 输出: Homo Sapiens
println(age)     // 输出: 18
```

---

### 3. 实现工厂模式:`apply` 方法

伴生对象最常用的场景之一是实现工厂模式。通过在伴生对象中**定义** `apply` 方法,你可以**省略** `new` 关键字来创建类的实例。这种语法糖让代码看起来更像是在调用一个函数。

#### 3.1 定义 `apply` 方法

**修改** `Person` 对象,**添加** `apply` 方法:

```scala
object Person {
  val defaultAge = 18

  // 定义 apply 方法,接收创建实例所需的参数
  def apply(name: String): Person = {
    // 使用默认年龄创建实例
    new Person(name, defaultAge)
  }
  
  def apply(name: String, age: Int): Person = {
    new Person(name, age)
  }
}
```

#### 3.2 使用工厂方法创建实例

**使用**简化的语法**实例化**对象。当编译器看到 `Person(...)` 时,它会自动**调用** `Person.apply(...)`。

```scala
// 方式 1:使用 new (传统方式)
val p1 = new Person("Alice", 25)

// 方式 2:使用 apply (工厂方式,省略 new)
val p2 = Person("Bob")        // 调用 apply(name: String)
val p3 = Person("Charlie", 30) // 调用 apply(name: String, age: Int)

println(p2.name) // 输出: Bob
println(p2.age)  // 输出: 18 (使用了默认值)
```

为了更直观地理解调用流程,请参考以下逻辑流向:

```mermaid
graph LR
    A[用户代码: Person] -->|隐式转换| B[伴生对象: apply]
    B -->|执行逻辑| C{参数匹配}
    C -->|name: String| D[返回: new Person, 18]
    C -->|name: String, age: Int| E[返回: new Person, age]
    D --> F[获得实例 p2]
    E --> G[获得实例 p3]
```

---

### 4. 互相访问私有成员

伴生对象与其伴生类之间可以**访问**彼此的 `private` 成员。这一特性打破了 Java 中 private 只能在类内部访问的限制,非常适合用于封装内部逻辑或共享配置。

#### 4.1 在类中访问对象的私有成员

**重构**代码,将一些配置项**设为**私有,并在类中**使用**它们。

```scala
class Person(val name: String, private val realAge: Int) {
  // 访问伴生对象的私有字段
  def getInfo(): String = {
    // Person.secretWord 是 private 的,但在类中可见
    s"Name: $name, Secret: ${Person.secretWord}"
  }
}

object Person {
  // 定义私有字段
  private val secretWord = "OpenSesame"
  
  def apply(name: String): Person = new Person(name, 18)
}
```

#### 4.2 在对象中访问类的私有成员

**编写**一个辅助方法,在伴生对象中**提取**类的私有信息。例如,假设我们需要一个工具来比较两个人的真实年龄,而不通过公开的 getter 方法。

```scala
class Person(val name: String, private val realAge: Int) {
  // ... 类定义保持不变
}

object Person {
  private val secretWord = "SecretCode"

  // 在伴生对象中定义方法,访问类的 private 字段 realAge
  def compareAge(p1: Person, p2: Person): Int = {
    // 直接访问 private realAge
    p1.realAge - p2.realAge
  }
  
  def apply(name: String): Person = new Person(name, 18)
}

// 使用示例
val alice = new Person("Alice", 30)
val bob = Person("Bob", 25) // age 默认 18,这里假设 apply 逻辑已修改为传入 25

// 调用伴生对象的比较方法
val ageDiff = Person.compareAge(alice, bob)
println(ageDiff) // 输出: 5
```

---

### 5. 定义隐式值与隐式转换

伴生对象是存放隐式转换和隐式参数的最佳场所。当编译器在当前作用域找不到隐式值时,它会自动去伴生对象中**查找**。

#### 5.1 定义隐式转换

**假设**你需要给 `Person` 类增加一个“展示详细资料”的方法,但你不能修改原有的 `Person` 类代码(或者为了保持代码整洁)。**定义**一个隐式类来实现。

```scala
// 仍然在 Person.scala 文件中,或者同一个包对象下
object Person {
  // ... 其他成员

  // 定义隐式类
  implicit class RichPerson(person: Person) {
    def showDetails(): Unit = {
      println(s"Details: ${person.name} is ${person.realAge} years old.")
    }
  }
}

5.2 使用隐式转换

导入或直接使用该隐式方法。由于隐式类定义在伴生对象中,只要 Person 类在作用域内,这个转换就会自动生效。

val charlie = Person("Charlie")

// 直接调用原本不存在的方法
// 编译器会自动找到 RichPerson 的包装
charlie.showDetails() // 输出: Details: Charlie is 18 years old.

6. 继承 App 特质作为程序入口

在 Scala 中,程序的入口点通常定义在伴生对象中,并继承 scala.App 特质。这样可以省略编写 def main(args: Array[String]): Unit = ... 的样板代码。

修改伴生对象定义如下:

object MyApp extends App {
  // 这里的代码直接作为 main 方法的函数体执行
  println("Application Started")

  val p = Person("David")
  println(p.introduce())
}

7. 核心特征对比表

下表总结了 Scala 中 class(类)与 companion object(伴生对象)的主要区别与联系。

特性 Class (类) Object (对象/伴生对象)
实例化 需要 new 关键字(或有 apply 方法) 不能实例化,本身就是一个单例
成员性质 实例成员(每个对象一份) 静态成员(全局唯一一份)
访问方式 通过实例引用(如 p.name 通过名称直接访问(如 Person.method
包含内容 构造器参数、实例方法、实例变量 apply 方法、工厂方法、静态常量、隐式值
私有访问 只能访问类自己的私有成员 可以访问伴生类的私有成员(双向互通)
继承 可以继承类和特质 可以继承特质(如 App),不能继承类

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文