Kotlin 继承
- 继承的目的:继承是面向对象编程中的一个核心概念,允许从现有类(基类)创建新类(派生类),继承并添加额外特性,减少代码重复,增加可读性和可扩展性。
- Kotlin 中的继承实现:在 Kotlin 中实现继承,基类必须用
open
关键字标记(因为 Kotlin 类默认是最终的)。派生类使用冒号:
继承基类,并可以拥有自己的特殊功能。 - 重写成 员函数和属性:在 Kotlin 中,如果基类和派生类有同名的成员函数或属性,需要用
override
关键字重写派生类的成员,并在基类中使用open
关键字。使用super
可以从派生类调用基类的成员。
[继承是面向对象编程的关键特性之一。它允许用户从现有的类(基类)创建一个新类(派生类)。
派生类继承了基类的所有特性,并可以拥有自己的额外特性。
在深入了解 Kotlin 继承之前,我们建议您查看这两篇文章:
为什么要用继承?
假设,在你的应用程序中,你想要三个角色 - 一个数学老师,一个足球运动员和一个商人。
由于所有的角色都是人,他们都可以走路和说话。然而,他们也有一些特殊技能。数学老师可以教数学,足球运动员可以踢足球,商人可以经营生意。
你可以分别创建三个具有走路、说话和执行特殊技能的类。
在每个类中,你需要为每个角色复制相同的走路和说话代码。
如果你想增加一个新特性 - 吃饭,你需要为每个角色实现相同的代码。这很容易出错(在复制时)并产生重复 代码。
如果我们有一个带有基本特性(如说话、走路、吃饭、睡觉)的 Person
类,并根据我们的角色为这些特性添加特殊技能,那将更容易。这就是使用继承所做的。
使用继承后,你就不需要为每个类实现相同的
walk()
、talk()
和 eat()
代码了。你只需要继承它们。
因此,对于 MathTeacher
(派生类),你继承了 Person
(基类)的所有特性,并添加了新特性 teachMath()
。同样,对于 Footballer
类,你继承了 Person
类的所有特性,并添加了新特性 playFootball()
,依此类推。
这使你的代码更加清晰、易懂和可扩展。
重要的是要记住: 在使用继承时,每个派生类都应该满足它是否是基类的**“是一个”**的条件。在上面的例子中,MathTeacher
是一个 Person
,Footballer
是一个 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
是基类,而 MathTeacher
、Footballer
和 Businessman
类是从 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 踢球。
这里,两个类 MathTeacher
和 Footballer
是从 Person
类派生出来的。
Person
类的主构造器声明了两个属性:age
和 name
,并且它有一个初始化块。基类 Person
的初始化块(和成员函数)可以被派生类的对象(MathTeacher
和 Footballer
)访问。
派生类 MathTeacher
和 Footballer
分别有它们自己的成员函数 teachMaths()
和 playFootball()
。这些函数只能从各自类的对象中访问。
当创建 MathTeacher
类的对象 t1
时,
val t1 = MathTeacher(25, "Jack")
参数传递给主构造器。在 Kotlin 中,当对象创建时会调用 init
块。因为 MathTeacher
是从 Person
类派生的,它会寻找基类(Person)中的初始化块并执行它。如果 MathTeacher
有 init 块,编译器也会执行派生类的 init 块。
接下来,使用 t1.teachMaths()
语句调用对象 t1
的 teachMaths()
函数。
当创建 Footballer
类的对象 f1
时,程序的工作方式类似。它执行基类的 init 块。然后,使用语句 f1.playFootball()
调用 Footballer
类的 playFootball()
方法。
重要说明:Kotlin 继承
-
如果类有主构造器,基类必须使用主构造器的参数来初始化。在上面的程序中,两个派生类都有两个参数
age
和name
,并且这两个参数都在基类的主构造器中初始化。下面是另一个例子:
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 次级构造器。