继承是面向对象编程的关键功能之一。它允许用户从现有类(基类)创建一个新类(派生类)。
派生类继承了基类的所有功能,并且可以拥有自己的其他功能。
为什么要继承?
假设在您的应用程序中需要三个角色-一个数学老师(MathTeacher),一个足球运动员(Footballer)和一个商人(Businessman)。
由于所有角色都是人,因此他们可以 走路 和 说话。但是,他们也有一些特殊技能。数学老师可以教数学(teachMath),足球运动员可以踢足球(playFootball),商人可以经营企业(runBusiness)。
您可以单独创建三个可以走路,说话和执行其特殊技能的类。
在每个类中,您将为每个角色复制相同的步行和说话代码。
如果要添加新特性 - eat(吃),则需要为每个角色实现相同的代码。这很容易导致出错(复制时)和重复代码。
如果我们有一个具有基本功能的 Person 类,比如说,走,吃,睡,并根据我们的角色为这些功能添加特殊技能,那就容易多了。这是通过继承完成的。
使用继承,现在您不需要为每个类实现相同的walk()、talk()和eat()代码。 你只需要继承它们就行了。
因此,对于MathTeacher(派生类),您可以继承Person(基类)的所有功能,并添加一个新功能 teachingMath()。 同样,对于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 类派生的。
注意,关键字 open 在基类 Person 之前,这点非常重要。
默认情况下,Kotlin中的类是最终的。 如果您熟悉Java,那么您将知道最终类不能被子类化。 通过在类上使用 注解,编译器允许您从其派生新类。
示例: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("我为洛杉矶银河队效力。")
}
}
fun main(args: Array<String>) {
val t1 = MathTeacher(25, "Jack")
t1.teachMaths()
println()
val f1 = Footballer(29, "Christiano")
f1.playFootball()
}
运行该程序时,输出为:
我的名字是 Jack.
我的年龄是 25
我在小学教书。
我的名字是 Cristiano.
我的年龄是 29
我为洛杉矶银河队效力。
这里,从 Person 类派生了两个 MathTeacher 和 Footballer 类。
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()函数。
创建类的对象 f1 时,该程序的工作原理类似。 它执行基类的init块。 然后,使用语句f1.playFootball()调用 Footballer 类的playFootball()方法。
重要说明:Kotlin继承
如果该类具有主要构造函数,则必须使用主要构造函数的参数来初始化基类。在上面的程序中,两个派生类都有两个参数 age 和 name,并且这两个参数都在基类的主构造函数中初始化。
这是另一个实例:示例
open class Person(age: Int, name: String) { // some code } class Footballer(age: Int, name: String, club: String): Person(age, name) { init { println("年龄为 $age 的足球运动员 $name,为 $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 times") } } class AuthLog: Log { constructor(_data: String): this("From AuthLog -> + $_data", 10) { } constructor(_data: String, _numberOfData: Int): super(_data, _numberOfData) { } }
重写成员函数和属性
如果基类和派生类包含具有相同名称的成员函数(或属性),则可能需要使用 override 关键字覆盖派生类的成员函数,并对基类的成员函数使用 open 关键字。
示例:重写成员函数
// 空的主构造函数
open class Person() {
open fun displayAge(age: Int) {
println("我的年龄是 $age.")
}
}
class Girl: Person() {
override fun displayAge(age: Int) {
println("我的虚拟年龄是 ${age - 5}.")
}
}
fun main(args: Array<String>) {
val girl = Girl()
girl.displayAge(31)
}
运行该程序时,输出为:
的虚拟年龄是 26.
在此,girl.displayAge(31) 调用派生类 Girl 的 displayAge() 方法。
您可以通过类似的方式覆盖基类的属性。
//空的主要构造函数
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.
正如您看到的,我们分别在派生类和基类中为 age 属性使用了 override 和 open 关键字。
从派生类调用基类成员
您可以使用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.