位置:首页 > Scala > Scala 伴生类、伴生对象与 `apply()`、`case class` 一次讲清

Scala 伴生类、伴生对象与 `apply()`、`case class` 一次讲清

时间:2026-08-24  |  作者:宇宙开黑者  |  阅读:0

目录

  1. 为什么 Scala 没有 static
  2. 什么是伴生类和伴生对象
  3. 编译结果与调用方式怎么理解
  4. `apply()` 为什么常和伴生对象一起出现
  5. `case class` 和普通类有什么不同
  6. 实际开发里怎么选

前言

很多从 Java 转到 Scala 的开发者,都会先被一个基础问题绊住:没有 `static` 之后,类级别的方法、常量和入口函数该怎么组织。本文从伴生类、伴生对象的定义讲起,再顺着 `apply()` 和 `case class` 往下拆,帮你看清它们各自解决什么问题,以及在日常开发里该如何选择。

很多刚从 Java 转到 Scala 的开发者,第一次接触伴生对象时都会卡在一个问题上:既然 Scala 没有 static,那类级别的方法和属性到底该放哪。理解这个设计后,再看 apply()case class,就会发现它们其实是一套连贯的语言机制。下面按“概念、调用、编译结果、典型场景”几个层次拆开说明,便于你快速建立判断标准。

为什么 Scala 没有 static

Scala 在语言设计上没有直接引入 static 关键字。常见的类级别属性、工具方法、程序入口等内容,通常放到 object 里定义。

可以先用一个便于迁移理解的说法:object 在很多场景下承担了 Java 里 static 的角色。

这并不意味着两者完全等价,但对初学者来说,这个映射足够帮助理解伴生对象为什么存在。

什么是伴生类和伴生对象

当同一个文件内同时存在同名的 class Xobject X 时,这一对定义就构成了伴生关系:

Scala 伴生类与伴生对象的职责和访问方式对照图
伴生类与伴生对象怎么分工用一张并列关系图说明伴生类与伴生对象如何分工,以及它们在访问方式和编译产物上的差异。
  • class Xobject X伴生类
  • object Xclass X伴生对象

这里有两个关键点:

1. 必须同名,通常配套出现

只有名字一致,才能形成标准的伴生关系。

2. 双方可以互相访问 private 成员

在 Scala 里,伴生类和伴生对象之间的边界比很多语言更弱,它们可以互相访问彼此的 private 字段和 private 方法。这也是伴生机制非常实用的原因之一。

编译结果与调用方式怎么理解

从编译结果看,伴生类和伴生对象并不是同一个东西,而是分别产出不同的字节码文件:

伴生类 class X 会编译成 X.class

伴生对象 object X 会编译成 X$.class

从调用方式看,两者也有明显区别:

  • 伴生对象中的属性或方法,可以直接通过类名调用
  • 伴生类中的属性或方法,必须先创建实例后再调用

如果只有 object、没有对应的 class,通常可以把它理解为单例对象的写法。

基础语法示例

class AssociatedDemo {
  val a:Int = 10;
  var b:Int = 2;
}

object AssociatedDemo{
  val object_v1:String = "变量1";
    
  def main(args: Array[String]): Unit = {
    // 注:只有伴生对象中可以定义主函数,类似于static修饰
    println("伴生对象变量:" + AssociatedDemo.object_v1);
    println("伴生类变量:" + (new AssociatedDemo()).a);
  }
}

输出结果:

伴生对象变量:变量1

伴生类变量:10

这个例子适合用来记住最常见的分工:

  • object AssociatedDemo 负责类级别访问入口
  • new AssociatedDemo() 负责实例级别状态

`apply()` 为什么常和伴生对象一起出现

apply() 是 Scala 很常见的一层语法糖。直接把类名或对象名当作函数来调用时,底层实际触发的就是对应的 apply() 方法。

Scala apply 调用路径示意图,区分类实例 apply 与伴生对象 apply
`apply()` 到底调用了谁这张图把 `AssociatedDemo()`、`ad2("AAA")` 两类调用拆开。

这也是 Scala 更偏函数式风格的一部分:很多场景下,你可以把“创建对象”写得更像一次普通调用。

在伴生对象里用 apply() 创建实例

一个典型用法,是把构造逻辑放进伴生对象的 apply() 中,从而省掉显式的 new

object AssociatedDemo {
    def apply():AssociatedDemo = new AssociatedDemo();
    def main(args: Array[String]): Unit = {
        // 变量复制,直接可以实例化对象,不需要再new了
        val ad1 = AssociatedDemo();
    }
}

这里的:

val ad1 = AssociatedDemo()

本质上等价于调用:

AssociatedDemo.apply()

class 的 apply() 和 object 的 apply() 有什么区别

apply() 不只可以写在 object 中,也可以写在 class 中。区别在于调用目标不同:

  • class 里的 apply(),要先拿到实例,再通过实例调用
  • object 里的 apply(),可以直接通过对象名调用

看下面这段代码就很清楚:

class AssociatedDemo {
  // class中的apply()方法
  def apply(param:String){
    println("class apply method called:" + param)
  }
}

object AssociatedDemo{
  // object中的apply()方法
  def apply(param:String){
    println("object apply method called:" + param)
  }
    
  def main(args: Array[String]): Unit = {
    // class 的apply()
    val ad2 = new AssociatedDemo();
    ad2("AAA")
    ad2("BBB")

    // object 的apply()
    AssociatedDemo("CCC")
    AssociatedDemo("DDD")
  }
}

Demo 执行结果:

class apply method called:AAA

class apply method called:BBB

object apply method called:CCC

object apply method called:DDD

判断规则可以直接记成一句话:

  • ad2("AAA") 调的是实例上的 apply()
  • AssociatedDemo("CCC") 调的是伴生对象上的 apply()

`case class` 和普通类有什么不同

case class(案例类)本质上仍然是类,但它更适合表示“以数据为中心、属性通常不可变”的对象。

Scala case class、case object 与 match 使用场景对比图
`case class` 的几个高频特性把 `case class` 的几个高频特性放在一张图里,便于和普通类形成快速对照。

case class 中默认就带有 apply(),因此创建对象时通常不需要写 new

原文把它类比为 Java 开发中的 entity 类,这种理解在入门阶段是有帮助的:它是一种更适合承载数据的特殊类定义形式。

按值比较,而不是按引用比较

case class 一个非常重要的特性,是对象比较更偏向“值语义”。示例如下:

case class Message(sender: String, recipient: String, body: String)

val message2 = Message("jorge@catalonia.es", "guillaume@quebec.ca", "Com va")
val message3 = Message("jorge@catalonia.es", "guillaume@quebec.ca", "Com va")
val messagesAreTheSame = message2 == message3  // true

虽然 message2message3 引用的不是同一个对象,但因为它们的值相同,所以 message2 == message3 的结果是 true

什么是 `case object`

没有参数的 case 形式,通常会写成 case object,而不是 case class。默认情况下,case object 是可序列化的。

case class CaseClass2(a:Int) extends SuperTrait         // Case class
case object CaseObject extends SuperTrait               // Case object

为什么 case class 适合配合 match 使用

case classcase object 经常与模式匹配 match 一起使用,这也是 Scala 中非常典型的写法:

// 定义案例类
abstract class Notification
case class Email(sender: String, title: String, body: String) extends Notification
case class SMS(caller: String, message: String) extends Notification
case class VoiceRecording(contactName: String, link: String) extends Notification

// match匹配对象类型
def showNotification(notification: Notification): String = {
  notification match {
    case Email(sender, title, _) => "Email"
    case SMS(number, message) => "SMS"
    case VoiceRecording(name, link) => "VoiceRecording"
  }
}

这种写法的优势在于:数据结构和分支处理天然对应,代码读起来非常直接。

实际开发里怎么选

如果你只是想快速判断该用哪种写法,可以按下面的思路来分:

  • 需要放类级别常量、工具方法、主函数入口:优先考虑 object
  • 需要表示可以实例化的对象状态:使用 class
  • 希望通过同名对象承载工厂方法、辅助方法:使用伴生对象
  • 对象主要用于承载不可变数据,并且经常参与比较或模式匹配:优先考虑 case class

可以把它理解为一条渐进路径:先掌握伴生类和伴生对象的职责分工,再理解 apply() 的调用入口,最后把 case class 看成对这套机制的进一步封装。

参考资料

  1. Scala之:伴生对象与静态概念 - 掘金 (juejin.cn)
  2. 《Scala程序设计:Ja va虚拟机多核编程实战》
  3. 案例类(Case Classes) | Scala Documentation (scala-lang.org)

免责声明:文中图文均来自网络,如有侵权请联系删除,心愿游戏发布此文仅为传递信息,不代表心愿游戏认同其观点或证实其描述。

相关文章

更多

精选合集

更多

大家都在玩

热门话题

大家都在看

更多