1. 简介
Scala 是一门融合了函数式编程与面向对象编程的语言,它能够无缝集成两种编程范式的特性。
在本教程中,我们将学习如何在 Scala 中实现面向对象编程的核心概念,包括:类的定义、封装、继承和多态等。
2. 类与对象
2.1. 定义类
在 Scala 中,我们使用 class
关键字来定义一个类:
class Bread
定义完类之后,可以使用 new
关键字创建该类的对象:
val whiteBread = new Bread
2.2. 定义字段
为了让类更有意义,我们可以为类添加字段。字段的定义方式类似于变量,使用 val
或 var
:
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