1. 简介

Scala 是一门融合了函数式编程与面向对象编程的语言,它能够无缝集成两种编程范式的特性。

在本教程中,我们将学习如何在 Scala 中实现面向对象编程的核心概念,包括:类的定义、封装、继承和多态等。

2. 类与对象

2.1. 定义类

在 Scala 中,我们使用 class 关键字来定义一个类:

class Bread

定义完类之后,可以使用 new 关键字创建该类的对象:

val whiteBread = new Bread

2.2. 定义字段

为了让类更有意义,我们可以为类添加字段。字段的定义方式类似于变量,使用 valvar

class Bread {
  val name: String = "white"
  var weight: Int = 1
}

之后,我们可以直接通过对象访问这些字段:

val whiteBread = new Bread
assert(whiteBread.name === "white")
assert(whiteBread.weight === 1)

由于 weight 是可变字段(使用 var),我们可以修改它的值:

whiteBread.weight = 2
assert(whiteBread.weight === 2)

2.3. 定义构造函数

构造函数用于在类中初始化字段或方法所需的数据。我们也可以在构造函数中定义默认值:

class Bread(breadName: String = "white") {
  val name: String = breadName
}

当我们传入参数构造对象时:

val grainBread = new Bread("grain")
assert(grainBread.name === "grain")

2.4. 定义方法

在类中定义方法使用 def 关键字。我们来给 Bread 类添加一个 getPrice 方法,用于根据重量和单价计算价格:

class Bread(breadName: String = "white", breadWeight: Int = 1) {
  val name: String = breadName
  var weight: Int = breadWeight

  def getPrice(priceUnit: Int): Int = {
    priceUnit * weight
  }
}

✅ 注意:Scala 中方法的返回值是最后一行表达式的结果,不需要显式写 return 关键字

创建对象后,我们可以调用该方法:

val bread = new Bread("grain", 2)
assert(bread.getPrice(2) === 4)

3. 封装

封装是面向对象编程的基本概念之一,它将数据和操作数据的方法封装在一个类中。

我们以一个表示 Sandwich 的类为例:

import scala.collection.mutable.ArrayBuffer

class Sandwich(bread: Bread, filling: ArrayBuffer[String]) {
  private def getFillingsName: String = {
    filling.mkString(", ")
  }
}

在这个类中,所有字段和方法都被定义为 private,因此我们无法从外部直接访问它们。尝试访问会报编译错误:

val sandwich = new Sandwich(new Bread("white"), ArrayBuffer("strawberry jam", "chocolate"))
sandwich.bread // error: value bread is not a member of Sandwich
sandwich.filling // error: value filling is not a member of Sandwich
sandwich.getFillingsName // error: method getFillingsName in class Sandwich cannot be accessed in Sandwich

我们可以提供公共方法来访问或操作这些私有成员:

def getDescription: String = {
  s"This is a sandwich with ${bread.name} bread and $getFillingsName filling"
}

def addFilling(extraFilling: String): Unit = {
  filling.append(extraFilling)
}

通过这些方法,我们就可以操作类的私有字段和方法:

val sandwich = new Sandwich(new Bread("sourdough"), ArrayBuffer("chicken"))
sandwich.addFilling("lettuce")
assert(sandwich.getDescription === "This is a sandwich with sourdough bread and chicken, lettuce filling")

4. 继承

继承是一种机制,允许我们基于一个已有的类创建新的类,新类可以复用原有类的字段和方法。

提供字段和方法的类称为 父类(superclass),继承的类称为 子类(subclass)。子类将继承父类中的所有非私有成员。

我们使用 extends 关键字来实现继承。首先定义一个父类 Vehicle

class Vehicle(val numWheels: Int, val color: String) {
  def accelerate(): Unit = { println("Vroom Vroom") }
}

然后定义一个子类 Bicycle 继承自 Vehicle

class Bicycle(bikeColor: String, val bikeType: String) extends Vehicle(2, bikeColor) {
  def maxSpeed(): Int = {
    bikeType match {
      case "road" => 60
      case _ => 20
    }
  }
}

此时,Bicycle 类继承了 Vehicle 的所有字段和方法,并添加了自己的字段 bikeType 和方法 maxSpeed()

val bicycle = new Bicycle("red", "road")
bicycle.accelerate()
assert(bicycle.numWheels === 2)
assert(bicycle.color === "red")
assert(bicycle.bikeType === "road")
assert(bicycle.maxSpeed() === 60)

5. 多态

多态是指同一个接口可以有不同的实现方式,Scala 中主要体现为方法重载(Overloading)和方法重写(Overriding)。

5.1. 方法重载

方法重载指的是在同一个类中使用相同的方法名,但参数列表不同,从而执行不同的逻辑。

我们来为 Bicycle 类中的 maxSpeed 方法添加一个重载版本,让它可以接受一个速度上限参数:

def maxSpeed(speedLimit: Int): Int = {
  bikeType match {
    case "road" => if (speedLimit < 60) speedLimit else 60
    case _ => if (speedLimit < 20) speedLimit else 20
  }
}

调用不同版本的 maxSpeed 方法,可以看到不同的结果:

val bicycle = new Bicycle("red", "road")
assert(bicycle.maxSpeed() === 60)
assert(bicycle.maxSpeed(10) === 10)

5.2. 方法重写

方法重写是指子类重新定义父类中已有的方法。在 Scala 中,重写方法时必须加上 override 关键字:

override def accelerate(): Unit = { println("Whoooosh") }

现在调用 accelerate() 方法将输出 "Whoooosh",而不是父类中的 "Vroom Vroom"

6. 总结

在本教程中,我们探讨了面向对象编程的基本概念,并演示了如何在 Scala 中实现类的定义、封装、继承和多态。

所有示例代码都可以在 GitHub 上找到: https://github.com/Baeldung/scala-tutorials/tree/master/scala-core-modules/scala-core-oop


原始标题:Object Oriented Programming in Scala