• Kotlin学习之路(五):继承


    前言

    Kotlin的继承和Java的继承一样都是单继承,区别在于Kotlin用:来代替了extends

    一. 类的继承

    Kotlin用:表示继承,Java用exteds表示继承。

    // 父类Person
    open class Person()
    
    // 子类Man
    class Man() : Person()
    
    // 简写
    class Man: Person()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    上面是最简单的Kotlin继承的例子。这里还需要注意的是在Kotlin中类默认都是final的(代表该类不让继承),需要在class关键词前面加上open,表示该类可以被其他类继承。和Java一样Kotlin也是单继承的。
    和Java的继承一样,子类可以使用父类的属性和方法:

    open class Person {
        var name: String = ""
    
        var age: Int = -1
    
        fun eat(){
            println("eat food!")
        }
    }
    
    class Man() : Person()
    
    fun main() {
        val man = Man()
        man.age = 22
        man.name = "Tom"
        println("age is ${man.age}")
        println("name is ${man.name}")
        man.eat()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    输出结果:

    age is 22
    name is Tom
    eat food!
    
    • 1
    • 2
    • 3

    从上面的代码可以看出,子类Man可以使用父类Person的属性和方法。

    二. 继承中的构造函数

    Kotlin在继承情况的下,构造函数的语法是怎么样的?首先来看看Java是如何处理的。

    2.1 继承中Java的构造函数

    Java是通过super来实现对父类构造函数的访问和传值,类似如下代码:

    // Person类(父类)
    public class Person {
        private String name;
    
        private int age;
    
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
    }
    
    // Man类(子类)
    public class Man extends Person{
    
        public Man(String name, int age) {
            super(name, age);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    通过关键字super可以将参数传递给父类,AndroidStudio中可以通过点击Create constructor matching super自动生成构造函数。接下来看看Kotlin是如何处理这个问题的。

    2.2 继承中Kotlin的构造函数

    在Kotlin中实现这种功能,只需要极少的代码量就可以实现:

    // Person
    open class Person(var name:String, var age:Int) {
        // Empty Code
    }
    
    // Man类
    class Man(name: String, age: Int): Person(name, age) {
    	// Empty Code
    }
    
    fun main() {
        val man = Man("Tom", 22)
        println("name is ${man.name} age is ${man.age}")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    打印结果:

    name is Tom age is 22
    
    • 1

    通过Man类的构造函数传递参数nameage,再通过: Person(name, age)传递到父类。
    有人可能要问了,如果不加上后面的: Person(name, age)是否可行?
    显然是不行的,Man类的构造函数没有var,那么它们就只是Man类构造函数的参数,不是类的成员变量。
    那么如果Man类加上var是否可以?
    答案也是不行的,这样父类和子类就会有两套成员变量,肯定会报错的。但是,我们可以重写父类的成员变量,后面会详细讲到。

    2.3 Kotlin多个构造函数的情况

    Kotlin是有主次构造函数之分的,如果一个父类有多个构造函数,那么子类是如何继承的呢?
    第一种写法:

    // Person类,这里有三个构造函数
    open class Person(var name: String, var age: Int) {
        constructor(name: String): this(name, -1)
        constructor(age: Int): this("", age)
    }
    
    // Man类
    class Man(name: String, age: Int) : Person(name, age) {
    	// Empty Code
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    父类有多个构造函数的情况,可以和上面一样这么写。但是,这样就不能以次构造函数的方式去创建一个对象。

    fun main(){
        val man = Man("Tom") // 报错!
        println("name is ${man.name} age is ${man.age}")
    }
    
    • 1
    • 2
    • 3
    • 4

    造成这种情况的原因是,我们只重写了父类的主构造函数,没有重写父类的次构造函数。针对这种情况,我们可以这样写。同样是上面的例子,代码如下:

    // Person类,这里有三个构造函数
    open class Person(var name: String, var age: Int) {
        constructor(name: String): this(name, -1) // 次构造函数
        constructor(age: Int): this("", age) // 次构造函数
    }
    
    // Man类
    class Man: Person {
        constructor(name: String, age: Int) : super(name, age) // 对应Person类的主构造函数
        constructor(name: String) : super(name) // 对应Person类的constructor(name: String)
        constructor(age: Int) : super(age) // 对应Person类的constructor(age: Int)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    直接将父类的三个次构造函数再复写一遍,用super指向父类的构造函数就行了。
    为了方便快速的构建,可以利用AndroidStudio的快捷创建方式:
    首先在类的内部点击鼠标右键,弹出如下菜单:
    构造函数快捷创建
    在弹出的菜单中点击Generate按钮,再弹出以下菜单:
    构造函数快捷创建

    最后点击Secondary Constructor按钮,选择你需要重写的构造函数:
    构造函数快捷创建
    通过这种快捷方式可以快速的重写父类的所有主次构造函数。

    三. 重写和重载

    这一章节重点讲解重写,至于重载,可以看我之前的文章Kotlin学习之路(三):函数
    中的1.3小结

    3.1 方法的重写

    代码还是上面的例子,只不过父类里面多一个测试方法:

    // Person
    open class Person(var name:String, var age:Int) {
    
        open fun testMethod(){ // 注意这里加上了open,表示可以被重写。默认情况下是fina
            println("父类test!")
        }
    }
    
    // Man类
    class Man(name: String, age: Int): Person(name, age) {
    
        override fun testMethod(){ // 类似Java的override重写标志
            println("子类test!")
        }
    }
    
    fun main(){
        val man = Man("Tom", 22)
        man.testMethod()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    输出结果:

    子类test!
    
    • 1

    这里需要着重介绍的一点的是,Kotlin的方法不加修饰符之前都是public final 的,特别是final ,所以在需要被重写的方法中加上open修饰符。
    重写方法需要遵循 两同、两小、一大的原则。

    • 两同:方法名相同,参数类型相同
    // Person
    open class Person(var name:String, var age:Int) {
    
        open fun testMethod(str: String){
            println("父类test! $str")
        }
    }
    
    // Man类
    class Man(name: String, age: Int): Person(name, age) {
    
        override fun testMethod(str: String){
            println("子类test! $str")
        }
    }
    
    fun main(){
        val man = Man("Tom", 22)
        man.testMethod("aaa")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 两小:子类返回值类型比父类返回值类型(返回值类型范围)小或相等、子类抛出异常类型比父类小或相等
    // Person
    open class Person(var name:String, var age:Int) {
    
        open fun testMethod(): Any?{
            println("父类test!")
            return null
        }
    }
    
    // Man类
    class Man(name: String, age: Int): Person(name, age) {
    
        override fun testMethod(): String{
            println("子类test!")
            return "test"
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    上面的例子父类的方法的返回值类型是Any?,子类的方法的返回值类型是String,从继承关系看,String继承于Any?,范围自然是Any?大于String。不过这种情况一般用的不多。

    • 一大:子类的修饰符权限比父类的修饰符权限大或相等
    // Person
    open class Person(var name:String, var age:Int) {
    
        protected open fun testMethod(): Any?{
            println("父类test!")
            return null
        }
    }
    
    // Man类
    class Man(name: String, age: Int): Person(name, age) {
    
        override fun testMethod(): String {// 如果这里改为private就会报错
            println("子类test!")
            return "test"
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    3.2 属性的重写

    属性的重写和方法的重写类似:

    // Person
    open class Person(var name:String, var age:Int) {
        open var test = -1 // 和方法一样,加上了open
    }
    
    // Man类
    class Man(name: String, age: Int): Person(name, age) {
        override var test = 0 // 这里加上了override标志
    }
    
    fun main(){
        val man = Man("Tom", 22)
        println("test value is ${man.test}")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    输出结果:

    test value is 0
    
    • 1

    从结果可以看出父类的成员变量test被子类重写了。一般情况下这种功能较少使用,重写属性可能会破坏里氏替换原则(子类可以扩展父类的功能,但不能改变父类原有的功能)

    四. super关键字

    Kotlin和Java一样也是有super关键字的。和Java的用法也是类似的,都是指代父类,用法也和Java相似。

    4.1 简单用法

    // Person
    open class Person(var name:String, var age:Int) {
        // Empty Code
    }
    
    // Man类
    class Man(name: String, age: Int): Person(name, age) {
    
        fun test(){
            println("name is ${super.name}")
        }
    }
    
    fun main(){
        val man = Man("Tom", 22)
        man.test()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    输出结果:

    name is Tom
    
    • 1

    上面的例子多少有点脱裤子放屁的意思,实际上可以去掉super,这里只是为了演示。它的主要作用还是用于方法重写上面。

    4.2 复杂情况下的用法

    这里介绍相对复杂的情况下的super的用法。

    4.2.1 子类重写方法中使用super

    在重写方法中使用super的方式基本和Java一样:

    // Person
    open class Person(var name:String, var age:Int) {
    
        open var test = -1
    
        open fun testMethod(){
            println("父类test!")
        }
    }
    
    // Man类
    class Man(name: String, age: Int): Person(name, age) {
    
        override var test = 0
    
        override fun testMethod() {
            super.testMethod() // 和Java的用法一样,调用父类Person的方法testMethod
            println("test value is ${super.test}") // 调用的是父类Person的test
        }
    }
    
    fun main(){
        val man = Man("Tom", 22)
        man.testMethod()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    输出结果:

    父类test!
    test value is -1
    
    • 1
    • 2

    4.2.2 子类选择性调用父接口/父类的方法

    当Kotlin的类继承父类和多个接口的时候,这就需要区分super调用的是哪一个父类的方法。Kotlin中通过super<父类/父接口>.方法方式调用父类/接口的方法

    // Australopithecus接口,写法和Java类似
    interface Australopithecus{
    
        fun testMethod(){
            println("接口test!")
        }
    }
    
    // Person
    open class Person {
    
        open fun testMethod(){
            println("父类test!")
        }
    }
    
    // Man类,既继承了父类Man,也实现了接口Australopithecus
    class Man: Person(), Australopithecus {
    
        override fun testMethod() {
            super<Australopithecus>.testMethod()
            super<Person>.testMethod()
        }
    }
    
    fun main(){
        val man = Man()
        man.testMethod()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    输出结果:

    接口test!
    父类test!
    
    • 1
    • 2

    4.2.3 子类内部类调用父类方法

    这种情况在于子类的内部类调用父类的方法,写法类似这样:super@子类.方法

    // Person
    open class Person {
    
        open fun testMethod(){
            println("父类test!")
        }
    }
    
    // Man类
    class Man: Person() {
    
    	// 被inner修饰的内部类是非静态内部类,静态内部类显然不能访问非静态内部类的成员方法
        inner class Heart{
        
            fun testMethod(){
                super@Man.testMethod()
            }
        }
    }
    
    fun main(){
        Man().Heart().testMethod()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    输出结果:

    父类test!
    
    • 1

    其实this在内部类中也有类似的用法:

    // Man类
    class Man {
    
        fun eat(){
            println("eat!")
        }
    
        inner class Heart{
    
            fun testMethod(){
                this@Man.eat() // 调用外部类的方法,这种操作更加常见
            }
        }
    }
    
    fun main(){
        Man().Heart().testMethod()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    输出结果:

    eat!
    
    • 1
  • 相关阅读:
    电脑出现找不到msvcp120.dll无法继续执行代码,不用担心多种方法帮你搞定
    【wxWidgets 实现Window窗口系统】
    Python实现猎人猎物优化算法(HPO)优化Catboost回归模型(CatBoostRegressor算法)项目实战
    Tp5中模型Model中字段类型转换与数据完成
    04_同步操作
    Zookeeper实战、选举原理、分布式锁原理
    SSM学习——SSM整合案例(Spring+SpringMVC+Mybatis)(13)
    华为诺亚ViG架构媲美CNN、Transformer,图神经网络也能用作CV骨干模型
    【youcans 的图像处理学习课】总目录
    postgresql(openGauss)模糊匹配参数
  • 原文地址:https://blog.csdn.net/RQ997832/article/details/125155911