在上一节中,我们掌握了如何定义独立的类和对象。本节,我们将深入探索类与类之间的关系,这是构建复杂、可扩展软件系统的基石。我们将重点学习Scala 中的继承、抽象类 和类型判断,这些概念共同构成了面向对象编程中多态性的核心。
思维导图


一、继承
继承是 面向对象编程中 实现代码复用和 建立类层次结构的 核心机制。它 允许一个类 (称为 子类或 派生类) 获取另一个类 (称为 父类或 基类) 的 非私有成员 (字段和方法)。1. 基本语法 (extends)
使用
extends 关键字来
声明一个类
继承自另一个类。
scala class Person(val name: String) { def sayHello(): Unit = println(s"Hello, I am $name.") } // Student 类继承自 Person 类 class Student(name: String, val studentId: String) extends Person(name) { def study(): Unit = println(s"$name is studying.") } val student = new Student("Alice", "S12345") student.sayHello() // 调用从父类继承的方法 student.study() // 调用子类自己的方法
2. 方法重写 (override)
子类可以 重新定义从父类 继承过来的 方法,以 提供自己的 特定实现。在 Scala 中, 重写父类方法 必须 使用
override 关键字。
3. 调用父类方法 (super)
在子类中,可以使用
super 关键字来
调用父类中
被重写的方法。
代码案例:
scala class Employee(name: String) extends Person(name) { // 使用 override 关键字重写父类的 sayHello 方法 override def sayHello(): Unit = { super.sayHello() // 使用 super 调用父类的原始方法 println("I am also an employee.") } } val employee = new Employee("Bob") employee.sayHello()
4. 防止继承与重写 (final)
>
final class ...:
不希望一个类
被任何其他类继承,可以将其
声明为 final。
final def ...:
不希望父类中的某个
方法被子类重写,可以将其
声明为 final。
## 二、抽象类
抽象类是 一种不能被 直接实例化的类,它 存在的目的就是 为了被其他类 继承。抽象类中可以 包含 抽象成员 (没有实现的字段或方法),用于 定义子类 必须实现的 “契约”。
语法特点:
> 使用
abstract class 关键字定义
抽象方法:只 声明方法签名, 没有方法体 (
= { ... })
抽象字段:只 声明字段名称和类型, 不赋初始值
代码案例:
// 定义一个抽象类 Shape
abstract class Shape {
// 抽象字段
val name: String
// 抽象方法
def area(): Double
// 普通方法
def printInfo(): Unit = {
println(s"This is a $name with area ${area()}")
}
}
// 子类继承抽象类,必须实现所有抽象成员
class Rectangle(val width: Double, val height: Double) extends Shape {
// 实现抽象字段
override val name: String = "Rectangle"
// 实现抽象方法
override def area(): Double = width * height
}
// val s = new Shape() // 错误:不能实例化抽象类
val rect = new Rectangle(10, 5)
rect.printInfo()
三、类型判断与转换
在具有继承关系的类层次中,我们经常需要判断一个父类引用实际指向的是哪个子类的对象,并可能需要将其转换为子类类型以调用其特有的方法。
| 操作 | 语法 | 描述 |
|---|---|---|
| 类型判断 | obj.isInstanceOf[Type] |
检查 obj 是否是 Type 类型或其子类型的实例。返回一个布尔值。 |
| 类型转换 | obj.asInstanceOf[Type] |
将 obj 的引用强制转换为 Type 类型。如果 obj 不是 Type 类型的实例,会抛出 ClassCastException 异常。 |
isInstanceOf
进行检查,
然后再安全地使用
asInstanceOf
进行转换。在
后续章节中,我们会学习
更优雅、更安全的
模式匹配 来
替代这种用法。
代码案例:
scala class Pet(val name: String) class Cat(name: String) extends Pet(name) { def meow(): Unit = println("Meow!") } class Dog(name: String) extends Pet(name) { def bark(): Unit = println("Woof!") } val myPet: Pet = new Cat("Mimi") // 类型判断 if (myPet.isInstanceOf[Cat]) { println("It's a cat!") // 安全地进行类型转换 val myCat = myPet.asInstanceOf[Cat] myCat.meow() } else if (myPet.isInstanceOf[Dog]) { println("It's a dog!") val myDog = myPet.asInstanceOf[Dog] myDog.bark() }
## 四、匿名内部类
有时,我们 只需要一个 类或抽象类的 一个临时、 一次性的 子类实例, 不想为其 显式地定义一个 完整的具名子类。这时,就可以使用 匿名内部类。
语法:
scala new SuperClassName/AbstractClassName { // 在这里重写或实现需要的方法 }
代码案例:
scala abstract class Greeter { def greet(): Unit } // 创建一个 Greeter 的匿名子类实例 val friendlyGreeter = new Greeter { override def greet(): Unit = { println("Hello and welcome!") } } friendlyGreeter.greet()
## 五、综合案例
这个案例将 综合运用本节所学的 所有知识点
代码实现:
// 1. 定义一个抽象基类
abstract class Animal(val name: String) {
// 抽象方法,子类必须实现
def makeSound(): String
// 普通方法
def info(): Unit = {
println(s"I am an animal named $name, I make a sound like: ${makeSound()}")
}
}
// 2. 定义具体的子类
final class Lion(name: String) extends Animal(name) {
override def makeSound(): String = "Roar!"
// Lion 特有的方法
def hunt(): Unit = println(s"$name is hunting.")
}
class Seal(name: String) extends Animal(name) {
override def makeSound(): String = "Arf! Arf!"
}
// 3. 在主程序中使用
object Zoo extends App {
val leo = new Lion("Leo")
val happy = new Seal("Happy")
// 4. 使用匿名内部类创建一个特殊的动物
val tweety = new Animal("Tweety") {
override def makeSound(): String = "Tweet!"
}
// 5. 将不同子类对象放入父类类型的集合中 (多态)
val animals: List[Animal] = List(leo, happy, tweety)
// 遍历集合并调用通用方法
for (animal <- animals) {
animal.info()
// 6. 类型判断与转换,调用子类特有方法
if (animal.isInstanceOf[Lion]) {
val lion = animal.asInstanceOf[Lion]
lion.hunt()
}
println("---")
}
}
练习题
题目一:简单继承
创建一个 Vehicle 类,包含一个 speed 成员变量。再创建一个 Car 类,继承自 Vehicle,并添加一个 brand 成员变量。
题目二:方法重写
为 Vehicle 类添加一个 describe() 方法,打印 "This is a generic vehicle."。在 Car 类中重写 describe() 方法,使其打印 "This is a car of brand [brand]."。
题目三:调用父类方法
修改 Car 类重写的 describe() 方法,使其在打印自己的信息前,首先调用父类的 describe() 方法。
题目四:final 关键字
如何修改 Car 类的定义,使其不能被任何其他类继承?
题目五:抽象类定义
定义一个抽象类 Shape,包含一个抽象方法 perimeter(): Double (计算周长) 和一个抽象字段 color: String。
题目六:实现抽象类
创建一个 Square 类,继承自 Shape。它需要一个 side (边长) 作为构造器参数,并实现 perimeter 方法和 color 字段 (假设颜色固定为 "Red")。
题目七:类型判断 isInstanceOf
创建一个 Square 实例和一个 Circle 实例 (假设 Circle 也继承自 Shape),将它们都赋值给 Shape 类型的变量。然后编写代码判断哪个变量实际上是 Square 的实例。
题目八:类型转换 asInstanceOf
在上一题的基础上,对于那个被确认为 Square 实例的变量,将其安全地转换回 Square 类型,并调用一个 Square 特有的方法 (例如 diagonal(),计算对角线,你需要自己添加这个方法)。
题目九:匿名内部类
创建一个 Shape 的匿名子类实例,用于表示一个边长为 5 的等边三角形。你需要即时实现其 perimeter 方法和 color 字段。
题目十:构造器与继承
创建一个父类 Person(val name: String) 和一个子类 Worker(name: String, val salary: Double) extends Person(name)。子类的构造器是如何将 name 参数传递给父类构造器的?
题目十一:受保护成员 (protected)
在 Person 类中添加一个 protected var age: Int。在 Worker 子类中添加一个方法 setAge(newAge: Int),证明子类可以访问并修改父类的 protected 成员。
题目十二:final 方法
在 Person 类中添加一个 final def getIdentity(): String = "A human being"。尝试在 Worker 子类中重写 getIdentity() 方法,观察会发生什么。
题目十三:类型判断的陷阱
如果一个对象 obj 是 Car 的实例,那么 obj.isInstanceOf[Vehicle] 的结果是 true 还是 false?为什么?
题目十四:多态的应用
创建一个 List[Shape],其中包含一个 Square 实例和一个 Triangle (可以是匿名类) 实例。遍历这个列表,并对每个元素调用 perimeter() 方法,打印其周长。
题目十五:综合案例扩展
在 Zoo 案例的 for 循环中,添加一个 else if 分支,用于判断 animal 是否是 Seal 的实例,如果是,则调用一个 Seal 特有的方法,例如 swim() (你需要为 Seal 类添加此方法)。
答案与解析
答案一:
class Vehicle {
var speed: Double = 0.0
}
class Car(val brand: String) extends Vehicle
解析: extends 关键字用于建立继承关系
答案二:
class Vehicle {
var speed: Double = 0.0
def describe(): Unit = println("This is a generic vehicle.")
}
class Car(val brand: String) extends Vehicle {
override def describe(): Unit = println(s"This is a car of brand $brand.")
}
解析: 子类重写父类方法必须使用 override 关键字
答案三:
// ... Vehicle 类定义 ...
class Car(val brand: String) extends Vehicle {
override def describe(): Unit = {
super.describe() // 调用父类方法
println(s"This is a car of brand $brand.")
}
}
解析: super.methodName() 用于在子类中调用父类的同名方法。
答案四:
final class Car(val brand: String) extends Vehicle { ... }
解析: 在 class 关键字前加上 final 可以阻止该类被继承。
答案五:
abstract class Shape {
val color: String
def perimeter(): Double
}
解析: 抽象方法和抽象字段都不需要实现或赋初始值。
答案六:
class Square(val side: Double) extends Shape {
override val color: String = "Red"
override def perimeter(): Double = side * 4
}
解析: 继承抽象类的子类必须使用 override 关键字实现所有抽象成员。
答案七:
val shape1: Shape = new Square(5)
// class Circle(val radius: Double) extends Shape { ... } // 假设 Circle 已定义
val shape2: Shape = new Circle(3)
if (shape1.isInstanceOf[Square]) {
println("shape1 is a Square.")
}
if (shape2.isInstanceOf[Square]) {
println("shape2 is a Square.")
} else {
println("shape2 is not a Square.")
}
解析: isInstanceOf[Type] 用于检查对象的运行时类型。
答案八:
// 首先为 Square 添加 diagonal 方法
// class Square(val side: Double) extends Shape { ... def diagonal(): Double = Math.sqrt(2) * side ... }
val shape1: Shape = new Square(5)
if (shape1.isInstanceOf[Square]) {
val sq = shape1.asInstanceOf[Square]
println(s"The diagonal is: ${sq.diagonal()}")
}
解析: 必须先通过 isInstanceOf 检查,再用 asInstanceOf 转换,这是安全的类型转换模式。
答案九:
val triangle = new Shape {
override val color: String = "Green"
override def perimeter(): Double = 5 * 3
}
println(s"A ${triangle.color} triangle with perimeter ${triangle.perimeter()}")
解析: new Shape { ... } 创建了一个 Shape 的匿名子类实例,并当场实现了其抽象成员。
答案十:
子类的构造器通过 extends Person(name) 这种语法将参数 name 传递给了父类 Person 的主构造器。
答案十一:
class Person(val name: String) {
protected var age: Int = 0
}
class Worker(name: String, val salary: Double) extends Person(name) {
def setAge(newAge: Int): Unit = {
// 可以访问和修改父类的 protected 成员
this.age = newAge
}
def getAge(): Int = this.age
}
val worker = new Worker("John", 50000)
worker.setAge(35)
println(worker.getAge()) // 输出 35
解析: protected 成员对子类是可见的。
答案十二:
在 Worker 子类中尝试重写 getIdentity() 方法会导致编译错误。
解析: final 关键字修饰的方法不能被子类重写。
答案十三:
结果是 true。
解析: isInstanceOf 检查的是“is a”关系。因为 Car 是 Vehicle 的一个子类,所以一个 Car 的实例也是一个 Vehicle 的实例。
答案十四:
val shapes: List[Shape] = List(new Square(4), new Shape {
override val color: String = "Blue"; override def perimeter(): Double = 3 + 4 + 5
})
for (shape <- shapes) {
println(s"A ${shape.color} shape with perimeter: ${shape.perimeter()}")
}
解析: 这是多态的体现。尽管
shapes列表的类型是List[Shape],但当调用shape.perimeter()时,程序会自动调用每个对象实际类型 (Square 或匿名类) 的perimeter方法实现。
答案十五:
// 首先为 Seal 类添加 swim 方法
// class Seal(name: String) extends Animal(name) { ... def swim(): Unit = println(s"$name is swimming.") ... }
// 在 Zoo 对象的 for 循环中修改
for (animal <- animals) {
animal.info()
if (animal.isInstanceOf[Lion]) {
val lion = animal.asInstanceOf[Lion]
lion.hunt()
} else if (animal.isInstanceOf[Seal]) { // 添加的分支
val seal = animal.asInstanceOf[Seal]
seal.swim()
}
println("---")
}
解析: if-else if 结构是处理多种不同子类类型的常用方式。