原文: https://www.programiz.com/kotlin-programming/inheritance

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

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

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

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


为什么是继承?

假设在您的应用中,您需要三个字符-一个数学老师,一个足球运动员和一个商人

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

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

Kotlin 继承 - 图1

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

如果要添加新函数eat,则需要为每个字符实现相同的代码。 这很容易导致出错(复制时)和重复代码。

如果我们有一个Person类,它具有基本的功能,例如说话,走路,吃饭,睡觉,并根据我们的角色向这些功能添加特殊技能,那就容易得多。 这是使用继承完成的。

Kotlin 继承 - 图2

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

因此,对于MathTeacher(派生类),您可以继承Person(基类)的所有函数并添加新函数teachMath()。 同样,对于Footballer类,您可以继承Person类的所有函数,并添加新函数playFootball(),依此类推。

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

重要的是要记住:在处理继承时,每个派生类都应满足“”基类的条件。 在以上示例中,MathTeacherPersonFootballerPerson。 您不能有BusinessmanBusiness这类的东西。


Kotlin 继承

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

  1. open class Person(age: Int) {
  2. // code for eating, talking, walking
  3. }
  4. class MathTeacher(age: Int): Person(age) {
  5. // other features of math teacher
  6. }
  7. class Footballer(age: Int): Person(age) {
  8. // other features of footballer
  9. }
  10. class Businessman(age: Int): Person(age) {
  11. // other features of businessman
  12. }

在此,Person是基类,并且MathTeacherFootballerBusinessman类是从Person类派生的。

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

默认情况下,Kotlin 中的类是最终的。 如果您熟悉 Java,那么您将知道最终类不能被子类化。 通过在类上使用开放修饰符,编译器允许您从其派生新类。


示例:Kotlin 继承

  1. open class Person(age: Int, name: String) {
  2. init {
  3. println("My name is $name.")
  4. println("My age is $age")
  5. }
  6. }
  7. class MathTeacher(age: Int, name: String): Person(age, name) {
  8. fun teachMaths() {
  9. println("I teach in primary school.")
  10. }
  11. }
  12. class Footballer(age: Int, name: String): Person(age, name) {
  13. fun playFootball() {
  14. println("I play for LA Galaxy.")
  15. }
  16. }
  17. fun main(args: Array<String>) {
  18. val t1 = MathTeacher(25, "Jack")
  19. t1.teachMaths()
  20. println()
  21. val f1 = Footballer(29, "Christiano")
  22. f1.playFootball()
  23. }

运行该程序时,输出为:

  1. My name is Jack.
  2. My age is 25
  3. I teach in primary school.
  4. My name is Cristiano.
  5. My age is 29
  6. I play for LA Galaxy.

这里,从Person类派生两个类MathTeacherFootballer

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

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


创建MathTeacher类的对象t1时,

  1. val t1 = MathTeacher(25, "Jack")

参数被传递给主构造器。 在 Kotlin 中,创建对象时会调用init块。 由于MathTeacher是从Person类派生的,因此它将在基类(Person)中查找初始化块并执行它。 如果MathTeacher具有初始化块,则编译器还将执行派生类的初始化块。

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

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


重要说明:Kotlin 继承

  • 如果类具有主要构造器,则必须使用主要构造器的参数来初始化基类。 在上面的程序中,两个派生类都有两个参数agename,并且这两个参数都在基类的主构造器中初始化。
    这是另一个示例: ```kt 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.”) }

  1. fun playFootball() {
  2. println("I am playing football.")
  3. }

}

fun main(args: Array) { val f1 = Footballer(29, “Cristiano”, “LA Galaxy”) }

  1. <br />在此,派生类的主要构造器具有 3 个参数,而基类具有 2 个参数。 请注意,基类的两个参数均已初始化。
  2. -
  3. 在没有主构造器的情况下,每个基类都必须初始化基函数(使用`super`关键字),或者委托给另一个执行该操作的构造器。 例如,
  4. ```kt
  5. fun main(args: Array<String>) {
  6. val p1 = AuthLog("Bad Password")
  7. }
  8. open class Log {
  9. var data: String = ""
  10. var numberOfData = 0
  11. constructor(_data: String) {
  12. }
  13. constructor(_data: String, _numberOfData: Int) {
  14. data = _data
  15. numberOfData = _numberOfData
  16. println("$data: $numberOfData times")
  17. }
  18. }
  19. class AuthLog: Log {
  20. constructor(_data: String): this("From AuthLog -> + $_data", 10) {
  21. }
  22. constructor(_data: String, _numberOfData: Int): super(_data, _numberOfData) {
  23. }
  24. }


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


覆盖成员的函数和属性

如果基类和派生类包含名称相同的成员函数(或属性),则可能需要使用override关键字覆盖派生类的成员函数,并对基类使用open关键字。


示例:覆盖成员函数

  1. // Empty primary constructor
  2. open class Person() {
  3. open fun displayAge(age: Int) {
  4. println("My age is $age.")
  5. }
  6. }
  7. class Girl: Person() {
  8. override fun displayAge(age: Int) {
  9. println("My fake age is ${age - 5}.")
  10. }
  11. }
  12. fun main(args: Array<String>) {
  13. val girl = Girl()
  14. girl.displayAge(31)
  15. }

运行该程序时,输出为:

  1. My fake age is 26.

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


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

在检查以下示例之前,请访问 Kotlin 获取器和设置器在 Kotlin 中的工作方式。

  1. // Empty primary constructor
  2. open class Person() {
  3. open var age: Int = 0
  4. get() = field
  5. set(value) {
  6. field = value
  7. }
  8. }
  9. class Girl: Person() {
  10. override var age: Int = 0
  11. get() = field
  12. set(value) {
  13. field = value - 5
  14. }
  15. }
  16. fun main(args: Array<String>) {
  17. val girl = Girl()
  18. girl.age = 31
  19. println("My fake age is ${girl.age}.")
  20. }

运行该程序时,输出为:

  1. My fake age is 26.

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


从派生类调用基类成员

您可以使用super关键字从派生类中调用基类的函数(和访问属性)。 这是如何做:

  1. open class Person() {
  2. open fun displayAge(age: Int) {
  3. println("My actual age is $age.")
  4. }
  5. }
  6. class Girl: Person() {
  7. override fun displayAge(age: Int) {
  8. // calling function of base class
  9. super.displayAge(age)
  10. println("My fake age is ${age - 5}.")
  11. }
  12. }
  13. fun main(args: Array<String>) {
  14. val girl = Girl()
  15. girl.displayAge(31)
  16. }

运行该程序时,输出为:

  1. My age is 31.
  2. My fake age is 26.