Skip to content

Scala 类与对象

Scala 是一种纯面向对象的语言,这意味着每个值都是一个对象。类(Class)是创建这些对象的蓝图,定义了它们的状态(字段,field)和行为(方法,method)。

基本的类定义包括 class 关键字、一个名称、构造函数参数以及包含字段和方法的主体。

// 具有可变状态的简单 Point 类
class Point(initialX: Int, initialY: Int) {
// 字段(与对象关联的变量)
var x: Int = initialX
var y: Int = initialY
// 方法(行为)
def move(dx: Int, dy: Int): Unit = {
x += dx // 等同于 x = x + dx
y += dy // 等同于 y = y + dy
}
// 将点表示为字符串的方法
override def toString: String = s"Point($x, $y)"
}

要点:

  • class Point(...):定义一个名为 Point 的类。
  • (initialX: Int, initialY: Int):主构造函数(primary constructor)。其参数在整个类体内可见。
  • var x: Int = initialX:定义一个可变字段 x,用构造函数参数 initialX 初始化。
  • def move(...):定义一个方法。
  • override def toString...:重写从 AnyRef 继承的默认 toString 方法。

使用 new 关键字后跟类名和构造函数参数来创建类的实例(对象)。

@main def ClassDemo(): Unit = {
// 创建 Point 类的一个实例
val p1 = new Point(10, 20)
println(s"Initial point: $p1") // 使用被重写的 toString 方法
// 调用对象上的方法
p1.move(5, -2)
println(s"Moved point: $p1")
// 直接访问字段(因为它们默认为 public)
p1.x = 100
println(s"Manually changed point: $p1")
}

输出:

Initial point: Point(10, 20)
Moved point: Point(15, 18)
Manually changed point: Point(100, 18)

Scala 提供了一种简洁的方式来将构造函数参数声明为字段,只需在参数前加上 val(不可变字段)或 var(可变字段)。

// 更简洁的 Point 类定义
// xc 和 yc 现在是公共的不可变字段
class ConcisePoint(val xc: Int, val yc: Int) {
// 我们仍然可以添加更多字段和方法
private var movedCount = 0
def move(dx: Int, dy: Int): Unit = {
// 注意:不能重新赋值 xc 或 yc,因为它们是 'val'
// 要使它们可变,可以定义为: class ConcisePoint(var xc: Int, var yc: Int)
println(s"Move called, but cannot change immutable Point($xc, $yc)")
movedCount += 1
}
def timesMoved: Int = movedCount
override def toString: String = s"ConcisePoint($xc, $yc)"
}

Scala 支持使用 extends 关键字进行单继承。子类会继承其超类(superclass)的非私有成员。

主要限制:

  1. 方法重写(Method overriding)需要 override 关键字。
  2. 只有子类的主构造函数可以将参数传递给超类构造函数。
class Point3D(override val xc: Int, override val yc: Int, val zc: Int)
extends ConcisePoint(xc, yc) { // 将参数传递给超类构造函数
var z: Int = zc // 附加字段
// 重写 move 方法(如果可能/需要)
// override def move(dx: Int, dy: Int): Unit = { ... }
// 添加 Point3D 特有的新方法
def move(dx: Int, dy: Int, dz: Int): Unit = {
// 由于 xc, yc 是 ConcisePoint 的 val,我们在这里也不能改变它们。
// 为了使这个例子有意义,我们假设 ConcisePoint 使用的是 var:
// super.move(dx, dy) // 如果 ConcisePoint 有可变的 x,y,则调用超类方法
z += dz
println(s"Moved 3D point to roughly ($xc, $yc, $z)") // 访问继承的 xc, yc
}
// 再次重写 toString
override def toString: String = s"Point3D($xc, $yc, $z)"
}
@main def InheritanceDemo(): Unit = {
val p3d = new Point3D(1, 2, 3)
println(p3d)
p3d.move(10, 20, 30) // 调用 Point3D 的 move 方法
println(p3d)
println(s"Times base class move logic called: ${p3d.timesMoved}") // 访问继承的方法
}

输出(假设基类的 move 方法更新状态):

Point3D(1, 2, 3)
Moved 3D point to roughly (1, 2, 33)
Point3D(1, 2, 33)
Times base class move logic called: 0 // Because ConcisePoint.move didn't modify state

Scala 没有像 Java 那样的静态成员。相反,它有 单例对象(singleton objects),使用 object 关键字定义。一个 object 定义会创建该类型的单个实例。

用途:

  • 存放工具方法或常量。
  • 实现单例设计模式(Singleton design pattern)。
  • 创建应用程序的入口点(entry points)(使用 @main 或 def main)。
  • 伴生对象(Companion objects)(见下文)。
object Logger {
private var level = "INFO"
def setLevel(newLevel: String): Unit = {
level = newLevel
}
def log(message: String): Unit = {
println(s"[$level] $message")
}
}
@main def ObjectDemo(): Unit = {
Logger.log("Application starting...")
Logger.setLevel("DEBUG")
Logger.log("Detailed diagnostic message.")
}

输出:

[INFO] Application starting...
[DEBUG] Detailed diagnostic message.

当一个类和一个单例对象共享相同的名称并定义在同一个源文件中时,它们被称为伴生对象(companions)。它们可以互相访问对方的私有成员。

用途:

  • 为类提供工厂方法(Factory methods)(通常在 apply 方法中实现)。
  • 存放与类相关但不特定于某个实例的方法或值(类似于 Java 中的静态成员)。
class Circle(radius: Double) { // 类
// 可以访问伴生对象 Circle 的私有成员
def area: Double = Circle.PI * radius * radius
private def secretMethod = "Instance secret"
def revealCompanionSecret = println(Circle.companionSecret)
}
object Circle { // 伴生对象
private val PI = 3.14159
private def companionSecret = "Companion secret"
// 使用 apply 方法的工厂方法
def apply(radius: Double): Circle = new Circle(radius)
// 可以访问类 Circle 的私有成员
def accessInstanceSecret(c: Circle) = println(c.secretMethod)
}
@main def CompanionDemo(): Unit = {
// 使用伴生对象的 apply 方法作为工厂
val c1 = Circle(5.0)
println(s"Circle area: ${c1.area}")
c1.revealCompanionSecret // 打印 "Companion secret"
Circle.accessInstanceSecret(c1) // 打印 "Instance secret"
}