Kotlin 继承

在本文中,您将学习继承。更具体地说,什么是继承以及如何在Kotlin中实现使用 继承(借助示例)。

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

派生类继承了基类的所有功能,并且可以拥有自己的其他功能。

在详细介绍Kotlin继承之前,建议您阅读以下两篇文章:

为什么要继承?

假设在您的应用程序中需要三个角色-一个数学老师(MathTeacher),一个足球运动员(Footballer)和一个商人(Businessman)

由于所有角色都是人,因此他们可以 走路 和 说话。但是,他们也有一些特殊技能。数学老师可以教数学(teachMath),足球运动员可以踢足球(playFootball),商人可以经营企业(runBusiness)

您可以单独创建三个可以走路,说话和执行其特殊技能的类。

不使用继承共享相同功能的类的示例。

在每个类中,您将为每个角色复制相同的步行和说话代码。

如果要添加新特性 - eat(吃),则需要为每个角色实现相同的代码。这很容易导致出错(复制时)和重复代码。

如果我们有一个具有基本功能的 Person 类,比如说,走,吃,睡,并根据我们的角色为这些功能添加特殊技能,那就容易多了。这是通过继承完成的。

OOP中的继承示例

使用继承,现在您不需要为每个类实现相同的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) {
        }
    }

      要了解有关该程序如何工作的更多信息,请访问Kotlin 次构造函数

重写成员函数和属性

如果基类和派生类包含具有相同名称的成员函数(或属性),则可能需要使用 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() 方法。

您可以通过类似的方式覆盖基类的属性。

在学习以下示例之前,可以访问 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.

正如您看到的,我们分别在派生类和基类中为 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.