跳到主要内容

Kotlin 继承

提示
  1. 继承的目的:继承是面向对象编程中的一个核心概念,允许从现有类(基类)创建新类(派生类),继承并添加额外特性,减少代码重复,增加可读性和可扩展性。
  2. Kotlin 中的继承实现:在 Kotlin 中实现继承,基类必须用 open 关键字标记(因为 Kotlin 类默认是最终的)。派生类使用冒号 : 继承基类,并可以拥有自己的特殊功能。
  3. 重写成员函数和属性:在 Kotlin 中,如果基类和派生类有同名的成员函数或属性,需要用 override 关键字重写派生类的成员,并在基类中使用 open 关键字。使用 super 可以从派生类调用基类的成员。

[继承是面向对象编程的关键特性之一。它允许用户从现有的类(基类)创建一个新类(派生类)。

派生类继承了基类的所有特性,并可以拥有自己的额外特性。

在深入了解 Kotlin 继承之前,我们建议您查看这两篇文章:

为什么要用继承?

假设,在你的应用程序中,你想要三个角色 - 一个数学老师,一个足球运动员和一个商人

由于所有的角色都是人,他们都可以走路和说话。然而,他们也有一些特殊技能。数学老师可以教数学,足球运动员可以踢足球,商人可以经营生意

你可以分别创建三个具有走路、说话和执行特殊技能的类。

没有使用继承的情况下,类共享相同特性的示例。 在每个类中,你需要为每个角色复制相同的走路和说话代码。

如果你想增加一个新特性 - 吃饭,你需要为每个角色实现相同的代码。这很容易出错(在复制时)并产生重复代码。

如果我们有一个带有基本特性(如说话、走路、吃饭、睡觉)的 Person 类,并根据我们的角色为这些特性添加特殊技能,那将更容易。这就是使用继承所做的。

面向对象编程中的继承示例 使用继承后,你就不需要为每个类实现相同的 walk()talk()eat() 代码了。你只需要继承它们。

因此,对于 MathTeacher(派生类),你继承了 Person(基类)的所有特性,并添加了新特性 teachMath()。同样,对于 Footballer 类,你继承了 Person 类的所有特性,并添加了新特性 playFootball(),依此类推。

这使你的代码更加清晰、易懂和可扩展。

重要的是要记住: 在使用继承时,每个派生类都应该满足它是否是基类的**“是一个”**的条件。在上面的例子中,MathTeacher 是一个 PersonFootballer 是一个 Person。你不能有像 Businessman 是一个 Business 这样的情况。

Kotlin 继承

让我们尝试在代码中实现上述讨论:

open class Person(age: Int) {
// 吃饭、说话、走路的代码
}

class MathTeacher(age: Int): Person(age) {
// 数学老师的其他特性
}

class Footballer(age: Int): Person(age) {
// 足球运动员的其他特性
}

class Businessman(age: Int): Person(age) {
// 商人的其他特性
}

这里,Person 是基类,而 MathTeacherFootballerBusinessman 类是从 Person 类派生出来的。

注意,基类 Person 前的关键字 open。这很重要。

默认情况下,Kotlin 中的类是最终的。如果你熟悉 Java,你知道最终类不能被子类化。通过在类上使用 open 注解,编译器允许

你从中派生新的类。

示例:Kotlin 继承

open class Person(age: Int, name: String) {
init {
println("我的名字是 $name。")
println("我的年龄是 $age")
}
}

class MathTeacher(age: Int, name: String): Person(age, name) {

fun teachMaths() {
println("我在小学教书。")
}
}

class Footballer(age: Int, name: String): Person(age, name) {
fun playFootball() {
println("我在 LA Galaxy 踢球。")
}
}

fun main(args: Array<String>) {
val t1 = MathTeacher(25, "Jack")
t1.teachMaths()

println()

val f1 = Footballer(29, "Cristiano")
f1.playFootball()
}

当你运行程序时,输出将是:

我的名字是 Jack。
我的年龄是 25
我在小学教书。

我的名字是 Cristiano。
我的年龄是 29
我在 LA Galaxy 踢球。

这里,两个类 MathTeacherFootballer 是从 Person 类派生出来的。

Person 类的主构造器声明了两个属性:agename,并且它有一个初始化块。基类 Person 的初始化块(和成员函数)可以被派生类的对象(MathTeacherFootballer)访问。

派生类 MathTeacherFootballer 分别有它们自己的成员函数 teachMaths()playFootball()。这些函数只能从各自类的对象中访问。

当创建 MathTeacher 类的对象 t1 时,

val t1 = MathTeacher(25, "Jack")

参数传递给主构造器。在 Kotlin 中,当对象创建时会调用 init 块。因为 MathTeacher 是从 Person 类派生的,它会寻找基类(Person)中的初始化块并执行它。如果 MathTeacher 有 init 块,编译器也会执行派生类的 init 块。

接下来,使用 t1.teachMaths() 语句调用对象 t1teachMaths() 函数。

当创建 Footballer 类的对象 f1 时,程序的工作方式类似。它执行基类的 init 块。然后,使用语句 f1.playFootball() 调用 Footballer 类的 playFootball() 方法。

重要说明:Kotlin 继承

  • 如果类有主构造器,基类必须使用主构造器的参数来初始化。在上面的程序中,两个派生类都有两个参数 agename,并且这两个参数都在基类的主构造器中初始化。

    下面是另一个例子:

open class Person(age: Int, name: String) {
// 一些代码
}

class Footballer(age: Int, name: String, club: String): Person(age, name) {
init {
println("足球运动员 $name 年龄 $age,效力于 $club。")
}

fun playFootball() {
println("我正在踢足球。")
}
}

fun main(args: Array<String>) {
val f1 = Footballer(29, "Cristiano", "LA Galaxy")
}

这里派生类的主构造器有 3 个参数,而基类有 2 个参数。注意,基类的两个参数都被初始化了。  

  • 在没有主构造器的情况下,每个基类必须使用 super 关键字来初始化基类,或者委托给另一个构造器来完成这个操作。例如,
fun main(args: Array<String>) {

val p1 = AuthLog("Bad Password")
}

open class Log {
var data: String = ""
var numberOfData = 0
constructor(_data: String) {

}
constructor(_data: String, _numberOfData: Int) {
data = _data
numberOfData = _numberOfData
println("$data: $numberOfData 次")
}
}

class AuthLog: Log {
constructor(_data: String): this("From AuthLog -> + $_data", 10) {
}

constructor(_data: String, _numberOfData: Int): super(_data, _numberOfData) {
}
}

要了解这个程序是如何工作的,请访问 Kotlin 次级构造器

重写成员函数和属性

如果基类和派生类包含同名的成员函数(或属性),你需要使用 override 关键字重写派生类的成员函数,并使用 open 关键字用于基类的成员函数。

示例:重写成员函数

// Empty primary constructor
open class Person() {
open fun displayAge(age: Int) {
println("My age is $age.")
}
}

class Girl: Person() {

override fun displayAge(age: Int) {
println("My fake age is ${age - 5}.")
}
}

fun main(args: Array<String>) {
val girl = Girl()
girl.displayAge(31)
}

当你运行程序时,输出将是:

我的假年龄是 26 岁。

这里,girl.displayAge(31) 调用了派生类 GirldisplayAge() 方法。

你可以以类似的方式覆盖基类的属性。

在查看下面的示例之前,访问了解 Kotlin 中的 getter 和 setter 是如何工作的。

// 空的主构造函数
open class Person() {
open var age: Int = 0
get() = field

set(value) {
field = value
}
}

class Girl: Person() {

override var age: Int = 0
get() = field

set(value) {
field = value - 5
}
}

fun main(args: Array`<String>`) {

val girl = Girl()
girl.age = 31
println("我的假年龄是 ${girl.age} 岁。")
}

当你运行程序时,输出将是:

我的假年龄是 26 岁。

如你所见,我们分别在派生类和基类中使用了 overrideopen 关键字来覆盖 age 属性。

从派生类调用基类的成员

你可以使用 super 关键字从派生类调用基类的函数(和访问属性)。以下是如何操作:

open class Person() {
open fun displayAge(age: Int) {
println("我的实际年龄是 $age 岁。")
}
}

class Girl: Person() {

override fun displayAge(age: Int) {

// 调用基类的函数
super.displayAge(age)

println("我的假年龄是 ${age - 5} 岁。")
}
}

fun main(args: Array`<String>`) {
val girl = Girl()
girl.displayAge(31)
}

当你运行程序时,输出将是:

我的实际年龄是 31 岁。
我的假年龄是 26 岁。