Kotlin 继承

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

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

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


为什么需要继承?

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

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

您可以单独创建三个类,它们都可以走路、说话并执行其特殊技能。

Example of classes sharing same features without the use of inheritance.

在每个类中,您都需要为每个角色复制相同的代码来走路和说话。

如果您想添加一个新功能——吃饭,您需要为每个角色实现相同的代码。这很容易出错(复制时)并导致代码重复。

如果有一个包含走路、说话、吃饭、睡觉等基本功能的Person类,然后根据我们的角色为这些功能添加特殊技能,那会容易得多。这就是通过继承来实现的。

Example of inheritance in OOP

通过继承,您现在不必为每个类实现相同的walk()talk()eat()代码。您只需要继承它们。

因此,对于MathTeacher(派生类),您继承了Person(基类)的所有功能,并添加了一个新功能teachMath()。同样,对于Footballer类,您继承了Person类的所有功能,并添加了一个新功能playFootball(),依此类推。

这使您的代码更简洁、更易于理解和扩展。

请务必记住: 在使用继承时,每个派生类都应满足其是否“是一个”基类。在上面的例子中,MathTeacherPersonFootballerPerson。您不能有这样的说法:BusinessmanBusiness


Kotlin 继承

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

open class Person(age: Int) {
    // code for eating, talking, walking
}

class MathTeacher(age: Int): Person(age) {
    // other features of math teacher
}

class Footballer(age: Int): Person(age) {
    // other features of footballer
}

class Businessman(age: Int): Person(age) {
    // other features of businessman
}

这里,Person是一个基类,而MathTeacherFootballerBusinessman类是从Person类派生的。

请注意,在基类Person前面有open关键字。这很重要。

默认情况下,Kotlin 中的类是 final 的。如果您熟悉 Java,您会知道 final 类不能被继承。通过在类上使用 open 注解,编译器允许您从此类派生新类。


示例:Kotlin 继承

open class Person(age: Int, name: String) {
    init {
        println("My name is $name.")
        println("My age is $age")
    }
}

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

    fun teachMaths() {
        println("I teach in primary school.")
    }
}

class Footballer(age: Int, name: String): Person(age, name) {
    fun playFootball() {
        println("I play for LA Galaxy.")
    }
}

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

    println()

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

运行程序后,输出将是

My name is Jack.
My age is 25
I teach in primary school.

My name is Cristiano.
My age is 29
I play for 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) {
        // some code
    }
    
    class Footballer(age: Int, name: String, club: String): Person(age, name) {
        init {
            println("Football player $name of age $age and plays for $club.")
        }
    
        fun playFootball() {
            println("I am playing football.")
        }
    }
    
    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关键字。


示例:重写成员函数

// 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)
}

运行程序后,输出将是

My fake age is 26.

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


您可以通过类似的方式重写基类的属性。

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

// Empty primary constructor
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("My fake age is ${girl.age}.")
}

运行程序后,输出将是

My fake age is 26.

正如您所见,我们在派生类和基类中分别为age属性使用了overrideopen关键字。


从派生类调用基类成员

您可以使用super关键字从派生类调用基类的函数(和访问属性)。方法如下:

open class Person() {
    open fun displayAge(age: Int) {
        println("My actual age is $age.")
    }
}

class Girl: Person() {

    override fun displayAge(age: Int) {

        // calling function of base class
        super.displayAge(age)
        
        println("My fake age is ${age - 5}.")
    }
}

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

运行程序后,输出将是

My age is 31.
My fake age is 26.
你觉得这篇文章有帮助吗?

我们的高级学习平台,凭借十多年的经验和数千条反馈创建。

以前所未有的方式学习和提高您的编程技能。

试用 Programiz PRO
  • 交互式课程
  • 证书
  • AI 帮助
  • 2000+ 挑战