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


一、继承
继承是面向对象编程中实现代码复用和建立类层次结构的核心机制。它允许一个类 (称为子类或派生类) 获取另一个类 (称为父类或基类) 的非私有成员 (字段和方法)。
1. 基本语法 (extends)
使用 extends 关键字来声明一个类继承自另一个类。
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 关键字来调用父类中被重写的方法。
代码案例:
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 进行转换。在后续章节中,我们会学习更优雅、更安全的模式匹配 来替代这种用法。
代码案例:
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()
}
四、匿名内部类
有时,我们只需要一个类或抽象类的一个临时、一次性的子类实例,不想为其显式地定义一个完整的具名子类。这时,就可以使用匿名内部类。
语法:
new SuperClassName/AbstractClassName {
// 在这里重写或实现需要的方法
}
代码案例:
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 结构是处理多种不同子类类型的常用方式。
- 点赞
- 收藏
- 关注作者
评论(0)