目录
第九章 继承
在Scala中, 继承(Inheritance) 是面向对象编程的重要概念, 允许一个类 (子类) 继承另一个类 (父类) 的特性和行为 ;
Scala中的继承通过 extends
关键字实现;
子类可以继承父类的字段、方法和特制 (traits) , 并可以重写父类的方法 .
1- 扩展类和重写方法
在Scala中, 扩展类 (Extending Classes) 是通过使用 extends
关键字来实现的 ;
当一个类扩展另一个类时, 他会继承父类的特性 (字段, 方法等) 并可以重写父类的方法 ;
当子类继承父类并重写的方法是, 需要使用 override
关键字来标记重写的方法 ;
示例:
// 定义父类
class Animal {
def sound(): Unit = {
println("Animal makes a sound !")
}
}
// 定义子类, 扩展自 Animal
class Dog extends Animal {
override def sound(): Unit = {
println("Dog barks !")
}
}
def main(args: Array[String]): Unit = {
// 创建子类对象
val dog = new Dog()
// 调用子类方法
dog.sound() // 输出: Dog barks !
}
- 在上面的示例中,
Dog
类扩展自Animal
类, 并重写了sound()
方法 ; - 通过扩展类,
Dog
类继承了Animal
类的特性, 并可以根据需要进行定制化 ; - 这种灵活性使得Scala中的继承和重写方法非常强大 ;
需要注意的是, 在Scala中, 一个类只能扩展一个类(单继承) , 到那时可以混入多个特质(traits) 来实现多重继承和代码复用 .
2- 类型转换和检查
在Scala中, 继承中的类型转换和检查可以通过 isInstanceOf
和 asInstanceOf
方法来实现 ;
示例:
// 定义父类
class Animal {
def sound(): Unit = {
println("Animal makes a sound !")
}
}
// 定义子类, 扩展自 Animal
class Dog extends Animal {
def bark(): Unit = {
println("Dog barks !")
}
}
def main(args: Array[String]): Unit = {
// 创建子类实例
val animal: Animal = new Dog()
// 类型检查
if (animal.isInstanceOf[Dog]) {
// 类型转换
val dog: Dog = animal.asInstanceOf[Dog]
dog.bark() // 输出: Dog barks !
} else {
println("Not a Dog !")
}
}
- 在上面的示例中, 我们首先创建了一个
Dog
类的实例, 并将其赋值给一个Animal
类型的变量 ; - 然后, 我们使用
isInstanceOf
方法检查变量的类型是否是Dog
, 如果是, 则使用asInstanceOf
方法将其转换为Dog
类型, 并调用bark()
方法 ; - 如果类型不是
Dog
, 则输出 “Not a Dog !”
需要注意的是, 在进行类型转换时, 应该谨慎处理可能出现的类型不匹配情况, 避免抛出 ClassCastException
异常 ;
因此, 在进行类型转换之前最好先进行类型检查 .
3- 受保护字段
在Scala中, 受保护字段 (Protected Fields) 是指只能在当前类、子类或伴生对象中访问的字段 ;
通过使用 protected
关键字来声明字段, 可以实现字段的受保护访问权限 ;
示例:
// 定义父类
class Animal {
protected var name: String = "Animal"
def getName(): String = {
name
}
}
// 定义子类, 继承自 Animal
class Dog extends Animal {
def setName(newName: String): Unit = {
name = newName
}
}
def main(args: Array[String]): Unit = {
// 创建子类实例
val dog = new Dog()
println(dog.getName()) // 输出: Animal
dog.setName("Buddy")
println(dog.getName()) // 输出: Buddy
}
- 在上面的示例中,
Animal
类中的name
字段被声明为受保护字段, 只能在Animal
类及其子类中访问 ; - 子类
Dog
继承了Animal
类, 并可以访问和修改name
字段 ; - 通过这种方式, 受保护字段可以提供一定程度的访问控制, 保护字段不被外部类直接访问 ;
4- 超类的构造
在Scala中, 子类在构造时需要调用超类(父类) 的构造器 ;
子类可以通过 extends
关键字继承超类, 并在子类的构造器中调用超类的构造器 ;
示例:
// 定义父类(超类)
class Person(val name: String, val age: Int) {
def displayInfo() = {
println(s"Name: $name, Age: $age")
}
}
// 定义子类, 继承父类 Person
class Employee(name: String, age: Int, val role: String) extends Person(name, age) {
def displayRole() = {
println(s"Role: $role")
}
}
def main(args: Array[String]): Unit = {
// 创建子类实例
val employee = new Employee("John Doe", 30, "Software Engineer")
// 调用父类方法
employee.displayInfo() // Output: Name: John Doe, Age: 30
// 调用子类方法
employee.displayRole() // Output: Role: Software Engineer
}
- 在上面的示例中,
Employee
类继承自Person
类, 并在其构造器中调用了Person
类的构造器 ; - 通过这种方式, 子类可以通过调用超类(父类) 构造器来初始化超类的字段 ;
- 这种机制确保了在构造子类实例时, 超类的构造器也会被正确调用 .
5- 重写字段和方法
在Scala中, 子类可以重写父类的 def
方法、val
字段和 var
字段 ;
当子类重写父类的 def
方法时, 子类可以提供新的实现 ;
当子类重写父类的 val
字段和 var
字段时, 子类可以重新定义字段的值 ;
示例:
// 定义父类
class Person {
def greet(): Unit = {
println("Hello, I am a Person !")
}
val name: String = "Person"
var age: Int = 26
}
// 定义子类, 继承父类, 重写父类的方法和字段
class Student extends Person {
override def greet(): Unit = {
println("Hello, I am a Student !")
}
override val name: String = "Student"
//override var age: Int = 18 // variable age cannot override a mutable variable
age = 18
}
def main(args: Array[String]): Unit = {
// 创建子类实例
val student = new Student()
student.greet() // Output: Hello, I am a Student !
println(student.name) // Output: Student
println(student.age) // Output: 18
}
- 在上面的示例中,
Employee
类继承自Person
类, 并重写了greet()
方法 和name
字段 ; - 通过
override
关键字, 子类Employee
提供了新的greet()
实现, 以及重新定义了age
和name
字段的值 ; - 当创建
Employee
类的实例时, 调用greet()
方法 将输出子类中重写的内容, 并访问age
和name
字段将返回子类中重写的值 ; - 注意: 在Scala中, 重写一个可变(mutable) 的变量 (var) 是不允许的 , 子类无法直接重写父类的
var
字段 ; - 子类继承父类的
var
字段, 并可以在子类中直接使用它 ;
6- 匿名子类
在Scala中, 可以使用匿名子类 (Anonymous Subclass) 来创建没有显示命名的子类 ;
匿名子类通常用于临时性的扩展或重写父类的行为, 而无需为其定义一个具名的类 ;
示例:
// 定义父类
class Person(val name: String) {
def greet(): Unit = {
println(s"Hello, My name is $name")
}
}
// 创建匿名子类并重写父类方法
val anonymousPerson = new Person("Anonymous") {
override def greet(): Unit = {
println(s"Hello,I am $name")
}
}
def main(args: Array[String]): Unit = {
// 调用匿名子类方法
anonymousPerson.greet() // Output: Hello,I am Anonymous
}
- 上面的示例中, 我们定义了一个
Person
类, 然后使用匿名子类重写了greet()
方法 ; - 通过创建一个没有显示类名的子类实例, 我们可以在其中提供新的实现 ;
- 这种方式适用于需要临时性的定制化行为或重写父类方法的情况 ;
匿名子类在Scala中是一种灵活且常用的技术, 可以帮助简化代码结构并实现临时性的定制化需求 .
7- 抽象类
在Scala中, 抽象类 (Abstract Classes) 是一种类似于 Java中抽象类的概念 ;
抽象类可以包含抽象方法 (没有具体实现的方法) 和非抽象方法 (有具体实现的方法) ;
与 Traits 相比, 抽象类可以包含构造函数参数, 而 Traits 不支持构造函数参数 ;
通常情况下, 当需要创建一个基类, 该基类需要构造函数参数或者需要被 Java 代码调用时, 可以使用抽象类 ;
示例:
// 定义一个抽象类
abstract class Animal(name: String) {
// 抽象方法, 子类必须实现
def makeSound(): Unit
// 非抽象方法, 具体实现
def eat(food: String): Unit = {
println(s"$name is eating $food")
}
}
// 定义一个子类, 继承抽象类
class Dog(name: String) extends Animal(name) {
// 实现抽象方法
override def makeSound(): Unit = {
println("Woof Woof!")
}
}
def main(args: Array[String]): Unit = {
// 创建一个Dog对象
val dog = new Dog("Buddy")
// 调用子类的方法
dog.makeSound() // Output: Woof Woof!
dog.eat("bones") // Output: Buddy is eating bones
}
- 在上面的示例中,
Animal
类是一个抽象类, 包含一个抽象方法makeSound()
和一个非抽象方法eat()
; - 子类
Dog
继承自Animal
类, 并实现了抽象方法makeSound()
; - 通过创建
Dog
类的示例, 我们可以调用抽象类中定义的方法 ;
抽象类在Scala中是一种重要的概念, 用于实现类似于 Java中的抽象类的功能 .
8- 抽象字段
在Scala中, 可以定义抽象字段 (Abstract Fields) 来要求字了提供具体的字段实现 ;
抽象字段可以是 val
(不可变) 或 var
(可变) ;
在抽象类中声明的抽象字段自行在子类中被具体化 ;
示例:
// 定义一个抽象类
abstract class Animal(name: String) {
// 抽象的不可变字段
val greeting: String
// 抽象的可变字段
var age: Int
// 具体方法
def sayHello(): Unit = {
println(s"$greeting, my name is $name")
}
// 抽象方法
def displayDetails(): Unit
}
// 定义一个子类, 继承抽象类
class Dog(name: String) extends Animal(name) {
// 实现抽象字段
val greeting: String = "Hello"
var age: Int = 3
// 实现抽象方法
override def displayDetails(): Unit = {
println(s"My name is $name, I am $age years old.")
}
}
def main(args: Array[String]): Unit = {
// 创建一个Dog对象
val dog = new Dog("Buddy")
// 调用子类的方法
dog.sayHello() // 输出: Hello, my name is Buddy
dog.displayDetails() // 输出: My name is Buddy, I am 3 years old.
}
- 在上面的示例中,
Animal
类是一个抽象类, 包含一个抽象的不可变字段greeting
和一个抽象的可变字段age
; - 子类
Dog
继承Animal
类, 并提供了具体的字段实现 ; - 通过创建
Dog
类的实例, 我们可以调用抽象类中定义的方法, 并访问具体的字段值 ;
抽象字段在 Scala中运行你定义需要再子类中提供具体实例的字段 ;
这种机制有助于实现灵活的类设计和继承结构 .
9- Scala继承层级
在Scala中, 继承层级的根是 Any
, 它是所有其他类的超类 ;
在Scala中, Any
有两个直接子类: AnyVal
和 AnyRef
;
AnyVal
是所有内建值类型的超类, 例如 Int
、Double
、Boolean
等 ;
而 AnyRef
则是所有引用类型(类) 的超类, 类似于 Java 中的 Object
;
在Scala中, 几乎所有的类都是 AnyRef
的子类, 因为他们都是引用类型 ;
AnyRef
继承在 Any
, 而 Any
又是所有类的超类 ;
这种继承层级关系确保了在Scala中所有类都是有一些通用的方法和属性, 例如 equals
、hashCode
等 ;
示例, 展示 Any
、AnyRef
和 AnyVal
之间的继承关系:
// Any 是Scala中的顶级父类,所有类的父类都是 Any。
val anyValue: Any = "Hello"
val anyRefValue: AnyRef = "Hello"
val anyValValue: AnyVal = 12
// AnyRef 是Scala中的顶级父类,所有引用类型的父类都是 AnyRef。
val stringValue: String = "Hello"
val listValue: List[Int] = List(1, 2, 3)
// AnyVal 是Scala中的顶级父类,所有值类型的父类都是 AnyVal。
val intValue: Int = 12
val doubleValue: Double = 12.0
- 在上面的示例中,
Any
是所有类的超类,AnyRef
是所有引用类型的超类, 而AnyVal
是所有值类型的超类 ; - 通过这种继承层级, Scala提供了一种统一的方式来处理不同类型的值和引用 ;
继承层级中的 Any
、AnyRef
和 AnyVal
是Scala中非常重要的概念, 有助于理解Scala中类之间的关系和类型系统 .
10- 对象相等性
在Scala中, 对象相等性有两种概念: 引用相等性 (Reference Equality) 和结构相等性 (Structural Equality) ;
-
引用相等性: 引用相等性是指两个对象是否指向内存中的同一位置 ; 在Scala中, 可以使用
eq
和ne
方法来检查两个对象是否引用同一个内存地址 .例如:
val a = "hello" val b = "hello" val c = new String("hello") println(a eq b) // 输出: true,因为字符串字面量通常会被优化为共享实例 println(a eq c) // 输出: false,尽管内容相同,但 c 是新创建的对象
-
结构相等性: 结构相等性是指两个对象的内容是否相等 ;
- 在Scala中, 可以使用
==
和!=
运算符来检查两个对象的内容是否相等 ; - 对于引用类型,
==
默认调用equals
方法进行比较 ;
示例:
val a = "hello" val b = "hello" val c = new String("hello") println(a == b) // 输出: true println(a == c) // 输出: true println(a.equals(b)) // 输出: true println(a.equals(c)) // 输出: true
- 在Scala中, 可以使用
- 除了上述方法外, Scala还提供了
equals
方法和hashCade
方法用于自定义对象的相等性比较; - 如果需要自定义对象的相等性逻辑, 可以重写
equals
方法和hashCode
方法 ;
总的来说, 在Scala中, 对象的相等性可以通过引用相等性和结构相等性来进行比较, 开发人员可以根据具体需求选择合适的比较方式 .