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

    重写成员函数和属性

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

    编程笔记 » Kotlin 继承