Bootstrap

Kotlin 入门第九天——继承、接口、扩展

一、继承

1. 定义一个Base类:

open class Base(p: Int) {

    open var x = 0
    open val y = 0
}

定义一个子类Child,继承Base类,要注意,这个子类没有构造函数.   子类没有构造函数,必须在每一个二级构造函数中用super关键字初始化基类,或者在代理另一个构造函数。

class Child(p: Int) : Base(p) {


}

引用Child方式:

val child = Child(100)

 再定义一个子类,这个子类有构造函数

2、有构造函数的子类

/**
 * 子类没有构造函数
 */
class Child : Base {
    /**
     * 子类没有够赞函数时,必须在每一个二级构造函数中用super关键字初始化基类,或者在代理另一个构造函数。初始化基类时,
     * 可以调用基类的不同构造方法
     */
    constructor(p: Int, s: String): super(p) {

    }

    //父类 x 是 var声明的,子类覆盖时,只能是var
    override var x: Int = 10
    //父类 y 是val,子类覆盖时,可以是val,也可以用var来声明。
    //因为val属性本身定义了getter方法,重写为var属性会在衍生类中额外声明一个setter方法
    override var y: Int = 20
}

因为val属性本身定义了getter方法,重写为var属性会在衍生类中额外声明一个setter方法  这句话应该好理解,

在父类 val 生命变量的时候,val是不可变的,因此只定义了getter方法,子类覆盖修改后,如果使用了var,var 是可变的,则属性会重新在子类声明setter方法。

二、接口

interface MyInterface {
    var name: String //属性,抽象的
    var age: Int
    fun bar()
    fun boo() {
        //kotlin的方法体中,可以实现方法
        println("boo MyInterface")
    }
}

与Java不同的是,kotlin接口可以定义字段

定义一个Child 类 来继承这个接口,注意name、age 与 boo() 方法在子类不同的表现

open class Child(override var name: String) : MyInterface, MyInterface2{

    //name 和 age 都是父类接口的属性,必须重写,重写方式有两种,一种在子类构造函数中,一种是如下
    override var age: Int
        get() = 10
        set(value) {}
    override fun bar() {
        println("Child bar")
    }

    override fun boo() {
        println("子类boo")
        super<MyInterface>.boo()
        super<MyInterface2>.boo()

    }
}

在外部调用Child 测试:(经过构造函数,name 变成了 zhangsan)

与Java不同的是,接口可以实现函数,子类可以覆盖函数的实现,但是覆盖后,父类函数依然有效,如 super<MyInterface>.boo() 的调用,打印出来的就是父类 boo() 方法的结果。

val c = Child("zhangsan")
        c.bar()
        c.boo()

打印结果如下:

三、扩展

在kotlin里面,扩展是区别Java独有的功能

对一个类的扩展,分为 属性扩展、函数扩展;在函数扩展里面,又有 空函数扩展、函数的静态解析扩展等

1、扩展函数

     /**
     * 扩展函数
     */
    fun extend() {
        var c = Child("zhangsan")
        c.bar()
        c.boo()
        //扩展函数
        c.FunTest()
        //扩展属性
        println("扩展属性的值: ${c.lastIndex}")
        val d = D("")
        printFoo(d) //最终的打印结果是 "Child",而不是"D";因为静态解析的话是根据printFoo 参数进行解析的,参数是C类,因此调用了C类的;若是动态解析,则调用d的方法了
        var ch: Child? = null
        println(ch.toString())

    }

    fun Child.FunTest() {
        //对类进行扩展,不修改类本身
        //扩展函数可以直接访问类的属性及方法
        println(name)
        //testExtend 是我在Child里面声明的一个函数,在扩展函数里面,可以直接调用被扩展对象的属性、方法
        testExtend()
    }

如上,我们引用了类 Child,并对Child 进行了函数扩展,扩展出来的一个函数叫做 FunTest(), 在扩展的函数里面,可以直接访问Child 的属性 name, 方法 testExtend()。

使用扩展函数有以下特点:

//对类进行扩展,不修改类本身
//扩展函数可以直接访问类的属性及方法

2、扩展属性:

val Child.lastIndex: Int
        get() = 10

对类 Child 进行属性扩展如上,新增属性 lastIndex ,访问值为 10

${c.lastIndex} 其中,$ 代表对其后第一个对象进行引用,如果没有{}, 则认为c 是要引用的对象,${},将{}内容作为了引用对象

3、扩展一个空函数

在扩展函数内, 可以通过 this 来判断接收者是否为 NULL,这样,即使接收者为 NULL,也可以调用扩展函数。例如:

fun Child?.toString(): String {
        if (this == null) return "result is null"
        return toString()
    }

使用如下,打印结果为: result is null (虽然c 是null,但是扩展的空函数还是被调用并打印出来了信息):

val c = null

print(c.toString())

4、扩展函数是静态解析的

fun Child.foo() = "Child"

如上,先扩展Child 的函数,结果为字符串 “Child”,在外部进行调用:

val c = Child()

c.foo() //最终的打印结果是 "Child"

需要注意的是:扩展函数是静态解析的,并不是接收者类型的虚拟成员,在调用扩展函数时,具体被调用的的是哪一个函数,由调用函数的的对象表达式来决定的,而不是动态的类型决定的:

;