原创

Kotlin中类的继承

Kotlin的继承也是单继承:每个子类最多只有一个直接父类。

一、继承的语法

语法格式如下:

修饰符  class  SubClass :SuperClass {
    //类定义部分
}

从子类的角度来看,子类扩展了父类;从父类的角度来看,父类派生了子类。

扩展和派生描述的是同一个动作,只是观察的角度不同而已

Any类是所有类的父类。

Kotlin的类默认就有final修饰,因此Kotlin的类默认是不能派生子类的。如果想让一个类能够派生子类,需要使用open修饰该类。

1.1、子类的主构造器

如果子类定义了主构造器,由于主构造器属于类头部分,为了让主构造器能调用父类构造器,必须在继承父类的同时委托调用父类构造器。

open class BaseClass {
    var name: String

    constructor(name: String) {
        this.name = name
    }
}

class SubClass : BaseClass("foo") {
}


class SubClass2(name: String) : BaseClass(name) {
}

SubClass没有显式声明主构造器。系统会为该类自动生成一个无参数的主构造器。

SubClass2显式定义了一个带参数的主构造器,因此程序同样需要在继承BaseClass时必须立即调用父类构造器。

1.2、子类的次构造器

次构造器同样需要委托调用父类构造器。

如果子类定义了主构造器,由于子类的次构造器总是委托调用子类的主构造器,而主构造器一定会调用父类构造器,所以子类的所有次构造器最终也调用了父类构造器。

如果子类没有定义主构造器,次构造器委托调用父类构造器可以分为3种方式:

  • 子类构造器显式使用:this(参数)显式调用父类中重载的构造器,系统将根据this(参数)调用中传入的实参列表调用本类中的另一个构造器。调用本类中的另一个构造器最终还是要调用父类构造器。
  • 子类构造器显式使用:super(参数)委托调用父类构造器,系统将根据super(参数)调用中传入的实参列表调用父类对应的构造器。
  • 子类构造器既没有:super(参数)调用,也没有:this(参数)调用,系统将会在执行子类构造器之前,隐式调用父类无参数的构造器。

Kotlin的次构造器相当于Java的构造器。它们唯一的区别是写法不同:

  • Java将调用父类构造器写在方法体内;
  • Kotlin将委托父类构造器写在构造器声明中。
open class Base {

    constructor() {
        println("Base的无参数的构造器")
    }

    constructor(name: String) {
        println("Base的带一个String参数:${name}的构造器;")
    }
}

class Sub : Base {

    //构造器没有显式委托
    //因此该次构造器将会隐式委托调用父类无参数的构造器
    constructor() {
        println("Sub的无参数的构造器")
    }

    //构造器用super(name)显式委托父类带String参数的构造器
    constructor(name: String) {
        println("Sub的String构造器,String参数为:${name}")
    }

    //构造器用this(name)显式委托本类中带String参数的构造器
    constructor(name: String, age: Int) : this(name) {
        println("Sub的String,Int构造器,Int参数为:${age}")
    }
}

fun main(args: Array<String>) {
    Sub()
    Sub("Sub")
    Sub("子类", 26)
}

输出结果:

Base的无参数的构造器
Sub的无参数的构造器
Base的带一个String参数:Sub的构造器;
Sub的String构造器,String参数为:Sub
Base的带一个String参数:子类的构造器;
Sub的String构造器,String参数为:子类
Sub的String,Int构造器,Int参数为:26
  • 第一个次构造器既没有super(参数)委托,也没有(this)委托,因此该构造器会隐式委托调用父类无参数的构造器。
  • 第二个次构造器使用:super(name)委托调用,其中name是一个String类型的参数,因此该构造器属于显式委托调用父类带一个String参数的构造器。
  • 第三个次构造器使用:this(name)委托调用,其中那么是一个String类型的参数,因此该构造器属于显式委托调用该类中带一个String参数的构造器,即调用前一个构造器;而前一个构造器委托调用了父类构造器,因此该次构造器最终委托调用了父类构造器。

注意:Kotlin与Java的设计相同,所有子类构造器必须调用父类构造器一次。

二、重写父类的方法

子类继承父类,将可以获得父类的全部属性和方法。

如下例:

open class Fruit(var weight: Double) {
    fun info() {
        println("我是一个水果,重${weight}克")
    }
}

class Apple : Fruit(1.0)

fun main(args: Array<String>) {
    //创建Apple对象
    var a = Apple()
    //Apple对象本身没有weight属性,但是父类有weight属性
    a.weight = 150.5
    a.info()
}

输出结果:

我是一个水果,重150.5克

子类继承了父类,子类是一个特殊的父类。大部分时候,子类总是以父类为基础,额外增加新的属性和方法。有时也需要重写父类的方法。

open class Bird {
    //Bird类的fly()方法
    open fun fly() {
        println("我在天空里自由自在地飞翔...")
    }
}

class Ostrich : Bird() {

    //重写Bird类的fly()方法
    override fun fly() {
        println("我只能在地上奔跑...")
    }
}

fun main(args: Array<String>) {
    //创建Ostrich对象
    var os = Ostrich()
    //执行Ostrich对象的fly()方法
    os.fly()
}

输出结果:

我只能在地上奔跑...

Kotlin中重写父类的方法必须添加override修饰符,Kotlin的override修饰符是强制的

方法的重写要遵循“两同两小一大”规则:

  • 方法名相同、形参列表相同;
  • 子类方法返回值类型比父类方法的返回值类型更小或相等,子类方法声明抛出的异常应比父类方法声明抛出的异常更小或相等;
  • 子类方法的访问权限应比父类方法的访问权限更大或相等。

如果父类方法具有private访问权限,则该方法对其子类是隐藏的,子类无法访问该方法,也就是无法重写该方法。

open class BaseClass {
    //test()方法具有private访问权限
    private fun test(){}
}

class SubClass : BaseClass() {
    //此处并不是方法重写,所以不用override修饰
    fun test(){}
}

三、重写父类的属性

父类被重写的属性必须使用open修饰,子类重写的属性必须使用override修饰。

重写属性还有两个限制:

  • 重写的子类属性的类型与父类属性的类型要兼容。
  • 重写的子类属性要提供更大的访问权限。子类属性的访问权限应比父类属性的访问权限更大或相等;只读属性可被读写属性重写,但读写属性不能被只读属性重写。
open class Item {
    open protected var price: Double = 10.9
    open val name: String = ""
    open var validDays: Int = 0
}

class Book : Item {
    //正确重写了父类属性,类型兼容,访问权限更大
    override public var price: Double
    //正确重写了父类属性,读写属性重写只读属性
    override var name = "图书"
    //重写错误,只读属性不能重写读写属性
    open val validDays: Int = 2

    constructor() {
        price = 3.0
    }
}

四、super限定

如果需要在子类方法中调用父类中被覆盖的方法或属性,则可使用super限定。

super是Kotlin提供的一个关键字,用于限定该对象调用它从父类继承得到的属性或方法。

open class BaseClass {
    open var a: Int = 5
}

class SubClass : BaseClass() {
    override var a: Int = 7
    fun accessOwner() {
        println(a)
    }

    fun accessBase() {
        //通过super限定访问从父类继承得到的a属性
        println(super.a)
    }
}

fun main(args: Array<String>) {
    val sc = SubClass()
    sc.accessOwner()//输出7
    sc.accessBase()//输出5
}

输出结果:

7
5

如果在某个方法中访问名为a的属性,但没有显示指定调用者,则系统查找a的顺序为:

  1. 查找该方法中是否有名为a的局部变量;
  2. 查找当前类中是否包含名为a的属性;
  3. 查找a的直接父类中是否包含名为a的属性,依次上溯查找a的所有父类,知道Any类,如果最终不能找到名为a的属性,则系统出现编译错误。

五、强制重写

如果子类从多个直接超类型继承了同名的成员,那么Kotlin要求子类必须重写该成员。

如果需要在子类中使用super来引用超类型中的成员,则可使用尖括号加速类型名限定的super进行引用,如super

open class Foo {
    open fun test() {
        println("Foo的test")
    }

    fun foo() {
        println("foo")
    }
}

interface Bar {
    //接口中成员默认是open的
    fun test() {
        println("Bar中的test")
    }

    fun bar() {
        println("bar")
    }
}

class Wow : Foo(), Bar {
    override fun test() {
        super<Foo>.test()//调用父类Foo的test()
        super<Bar>.test()//调用父接口Bar的test()
    }
}

fun main(args: Array<String>) {
    var w = Wow()
    w.test()
}

输出结果:

Foo的test
Bar中的test

学海无涯苦作舟

我的微信公众号.jpg

基本语法
  • 作者:HunterArley (联系作者)
  • 发表时间:2019-11-26 10:04
  • 版权声明:本网站部分内容转载于合作站点或其他站点,但都会注明作/译者和原出处。如有不妥之处,敬请指出。
  • 公众号转载:请在文末添加作者公众号二维码