侧边栏壁纸
博主头像
Z同学博主等级

工作磨平激情前,坚持技术的热忱。 欢迎光临Z同学的技术小站。 分享最新的互联网知识。

  • 累计撰写 274 篇文章
  • 累计创建 55 个标签
  • 累计收到 74 条评论

Kotlin学习笔记-继承,接口和扩展

Z同学
2021-07-14 / 0 评论 / 0 点赞 / 461 阅读 / 8,172 字
温馨提示:
本文最后更新于 2021-12-03,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

继承

介绍:继承主要就是为了重构或者实现父类定义的抽象方法。那么能从父类继承的模块主要分为:构造器,函数,属性。

Kotlin之中所有的类都继承Any类它是所有类的超类。可以参照java之中的所有类都是继承Object类来进行理解。

Any 类默认提供了三个函数:

    public open operator fun equals(other: Any?): Boolean

    public open fun hashCode(): Int

    public open fun toString(): String

这三个函数很好理解。java之中也一样都有这三个函数。

实例:

//定义一个基类, 构造传参name
open class Person(var name: String) {
}

//继承Person 类,实现了初始化构造
class Boy(name: String) : Person(name) {
    
    //重构了toString方法
    override fun toString(): String {
        return name
    }
}


fun main(array: Array<String>) {
    var boy = Boy("Z同学")
    println(boy.toString())
}

输出:

Z同学

重构构造器

如果子类有主构造器, 则基类必须在主构造器中立即初始化。

实例1:

//定义一个基类, 构造传参name
open class Person(var name: String) {
}

class Girl(name:String,age:Int):Person(name){
    
    //子构造器配置参考
    constructor(name:String,address:String,age:Int):this(name,age){
        
    }
}

如果子类没有构造器,但是父类有主构造器。则必须在每一个二级构造器之中用super关键字初始话父类。和java之中继承的时候需要采用super初始话父类的构造函数一样的要求。

实例2:

class Baby : Person {

    constructor(name: String) : super(name) {

    }

    constructor(name: String, six: Int) : this(name) {

    }
}

重构函数

介绍:在父类中使用fun声明的函数默认修饰final。代表该函数不允许被重构。

子类只能使用该函数不能重构。只有父类标注了'open' 关键字的函数才能被重构。

在子类之中标注了'override' 代表该函数重构于父类的函数

//定义一个基类, 构造传参name
open class Person(var name: String) {

    //可以继承,但是不能重构
    fun name(): String {
        return name
    }

    open fun study() {
        println("")
    }
}


class Student(name: String) : Person(name) {
    override fun study() {
        println("我还在读书,没有毕业")
    }
}


fun main(array: Array<String>) {
    var student = Student("A同学")
    student.name()
    student.study()
}

输出:

我还在读书,没有毕业

说明:open字段告诉了我们可以重构。并不代表我们必须重构该方法。除非该方法为抽象方法(abstract关键字)。

如果通过继承得到了多个相同的方法,那么必须重构该方法使用super关键字去选择性的调用父类的实现。也可以不执行父类的调用。

实例:

//可继承的类,A
open class A {
    open fun a() {
        println("A_a")
    }

    open fun b() {
        println("A_b")
    }
}

//接口对象B, 注意:接口的函数全部默认open。 不用特意添加open
interface B {
    fun a() {
        println("B_a")
    }

    fun b() {
        println("B_b")
    }
}

class C : A(), B {
    override fun a() {
        super<A>.a()  //
        super<B>.a()  // 全部注销也不报错
    }

    override fun b() {
        super<A>.a()
    }
}

fun main(array: Array<String>) {
    var c = C()
    c.a()
    c.b()
}

输出:

A_a
B_a
A_a

Kotlin的继承接口和普通类。两个类如果有相同的函数。在子类之中该函数只会有一个实现。但是需要在函数之中,决定使用哪个父类的实现

注意,Kotlin的类也只能继承一个父类。可以继承多个接口。

重构属性

介绍:其实重构属性,大部分都是为了能重写属性的gettersetter。重构属性也一样和重构函数一样。通过open函数开放属性的权限。通过override定义属性重构

实例:

open class Foo {
    var name: String = ""
    open val x: Int = 0
        get() = field
}

class Bar1 : Foo() {
    override val x: Int = 1
        get() {
            println(field)
            return field
        }
}

fun main(array: Array<String>) {
    var na = Bar1()
    na.x
}

注意:可以用var属性重构一个val属性,但是反之则不行。

因为val只定义了getter方法,重写为var属性时会在子类衍生类之中额外声明一个setting方法。破坏了val定义

接口

介绍:Kotlin之中的接口定义与java类似。也是使用interface 关键字进行定义。

不同于java,kotlin接口类允许方法默认实现。

实例:

interface TestInterface {
    fun test1()
    fun test2(str: String) {
        println("test2:${str}")
    }
}

一个类或者对象可以实现多个接口

实例:

interface TestInterface {
    fun test1()
    fun test2(str: String) {
        println("test2:${str}")
    }
}

class  TT:TestInterface{
    override fun test1() {
        TODO("重构接口抽象函数")
    }
}

相对于继承class,继承interface不用添加构造器。

属性

介绍:Kotlin中接口可以存在属性。但是该属性值不允许初始化。也就是赋值。

interface Test1Interface{
    var name:String //抽象的属性
}
class TT1:Test1Interface{
    override var name: String = "zz"

}

重构函数

介绍:Kotlin之中的接口对象,可以有已经实现的函数。我们继承之后只有未实现的函数我们需要强制实现。

interface A1 {
    fun a1()
    fun aa1(){  
    }
}

interface A2 {
    fun a2()
}

class BB:A1,A2{
    override fun a1() {
        TODO("Not yet implemented")
    }

    override fun a2() {
        TODO("Not yet implemented")
    }
}

扩展

介绍:Kotlin可以对一个类的属性和方法进行扩展,并且不需要继承。扩展之后的函数,其他类都可以进行调用。

函数扩展

介绍:可以在已有类之中,添加新的方法。不会对原类做任何修改。
可以对Kotlin所有的类都进行扩展,添加新的函数。

如果类实现的函数,不满足我们的需求,我们可以随意定义,新增扩展使用。

扩展函数的格式为:

fun 类名.扩展函数名(){

实现主体

}

实例1:

class User(var name: String){
    
}
/**
 * 定义的扩展函数
 */
fun User.tuozan() {
    print("用户名:$name")
}

fun main(array: Array<String>) {
    var user = User("Z同学")
    user.tuozan()
}

输出:

用户名:Z同学

实例2:

fun MutableList<Int>.swap(index1: Int, index2: Int) {
    val tmp = this[index1] //这个this对象就是MutableList对象的实例
    this[index1] = this[index2]
    this[index2] = tmp
}

fun main(array: Array<String>) {
    val te = mutableListOf<Int>(1,2,3)
    te.swap(0,2)//将下标0和下标2的值互换

    println(te.toString())
}

输出:

[3, 2, 1]
静态解析

介绍:扩展的函数是静态解析的。在调用扩展函数时,具体被调用的是哪个函数是由调用函数的对象表达式来决定的,而不是动态的类型决定的。

实例1:

//定义可继承类Z
open class Z

//定义类D继承于Z
class D : Z()

//给类Z创建一个扩展函数
fun Z.test1() = "Z 的test1函数"

fun main(array: Array<String>) {
    var d = D()
    print( d.test1())

}

输出:

Z 的test1函数

实例2:

//定义可继承类Z
open class Z

//定义类D继承于Z
class D : Z()

//给类Z创建一个扩展函数
fun Z.test1() = "Z 的test1函数"

fun D.test1() = "D的test1函数"

fun main(array: Array<String>) {
    var d = D()
    print(d.test1())
}

输出:

D的test1函数

实例3:

//定义可继承类Z
open class Z

//定义类D继承于Z
class D : Z()
//给类Z创建一个扩展函数
fun Z.test1() = "Z 的test1函数"

fun D.test1() = "D的test1函数"

fun ttTest(z: Z) {
    println(z.test1())
}

fun main(array: Array<String>) {
    var d = D()
    println(d.test1())
    ttTest(D())
    ttTest(Z())
}

输出:

D的test1函数
Z 的test1函数
Z 的test1函数
扩展已有函数

介绍:当创建的扩展函数与成员函数一致,那么编译器执行时会优先使用成员函数。

实例:

class Y {
    fun test1() {
        println("类的成员函数")
    }
}

fun Y.test1() {
    println("类的扩展函数")
}

fun main(array: Array<String>) {
    var y = Y()
    y.test1()
}

输出:

类的成员函数
扩展一个空对象

介绍:在扩展函数里,可以通过this关键字来判断接收者是否为Null。这样实现接收者为Null也可以调用扩展函数。

实例1:

fun main(array: Array<String>) {
    var t= null
    println(t.toString())
}

输出:

null

实例2:

fun Any?.toString(): String {
    if (this == null) return "针对Any Null 进行扩展"
    // 空检测之后,“this”会自动转换为非空类型,所以下面的 toString()
    // 解析为 Any 类的成员函数
    return toString()
}

fun main(array: Array<String>) {
    var t= null
    println(t.toString())
}

输出:

针对Any Null 进行扩展

在这种情况下。扩展函数优先执行了。
因为toString()成员函数是需要在非null情况下调用。

而扩展函数添加了?定义可以在非空模式下执行。

例如:

class Y {
    fun test1() {
        println("类的成员函数")
    }
}

fun Y?.test1() {
    println("类的扩展函数")
}
fun main(array: Array<String>) {
    var y: Y? = null
    y.test1()
}

输出:

类的扩展函数

可以在成员函数定义的情况下,我们额外针对空值情况下。调用新的方法

属性扩展

介绍:Kotlin除了函数,也支持对属性的扩展。

注意:扩展属性允许定义在类中,或者kt文件之中。但是不能定义在函数中。

也就是说针对某个类的属性进行扩展。不能在fun 函数里面写

实例:

//正确写法
val <T> List<T>.lastIndex:Int
    get() = size-1


fun main(array: Array<String>) {
    //错误写法
    val <T> kotlin.collections.List<T>.lastIndex:Int
    get() = size-1
}

由于扩展属性的定义关系,所以拓展属性没有后端字段,也就是说不能使用field方法。只能使用显式提供的gettersetter方法

实例:

class Zx {
    var name: String = "Zx"
}

var Zx.xx: String
    get() {
        if (name.equals("Zx")) {
            return "XXX结果"
        }
        return ""
    }
    set(value) {
        
    }

fun main(array: Array<String>) {
    var zx = Zx()
    println(zx.xx)
}

输出

XXX结果

伴生对象扩展

介绍:如果一个类定义有一个伴生对象,也可以为伴生对象定义函数或者属性的扩展。

伴生对象通过:"类名." 形式调用伴生对象。伴生对象声明的扩展函数,通过类名限定符来调用。

PS:之后会详细介绍伴生对象的概念。现在先让我们简单使用一下。

实例:

class MyClass {
    companion object {}
}

//创建伴生对象的扩展函数
fun MyClass.Companion.test() {
    println("伴生对象的扩展函数 test1")
}
//创建伴生对象的扩展属性
var MyClass.Companion.Id: Int
    get() {
        return 12
    }
    set(value) {
        Id = value
    }

fun main(array: Array<String>) {
    println("ID:${MyClass.Id}")
    MyClass.test()
}

输出:

ID:12
伴生对象的扩展函数 test1

作用域扩展

介绍:通常,扩展函数或者属性定义在一个包结构下。

要使用所定义包之外的一个扩展, 通过import导入扩展的函数名进行使用

可以参考类似于在同包目录下,我们可以直接使用java类不用添加import。但是在不同包路径我们就需要添加import 扩展也是一样的。

实例:

//文件1
package cn.zinyan.demo.kotlin.demo1

class demo1 {
}

fun demo1.test1(){
    println("demo1的扩展函数")
}
//文件2 
package cn.zinyan.demo.kotlin.demo2
//添加导入,才能使用
import cn.zinyan.demo.kotlin.demo1.demo1
import cn.zinyan.demo.kotlin.demo1.test1

class demo2 {
}

fun main(array: Array<String>) {
  var test= demo1()
    test.test1()
}

扩展声明为成员

介绍:在一个类内部,你可以为另一个类声明扩展。

注意:我们说的扩展不能在函数fun里面声明,但是可以在kt文件里面声明,可以在class里面声明。

在一个扩展中,有个多个隐含的接受者,其中扩展方法定义所在类的实例称为分发接受着,而扩展方法的目标类型的实例称为扩展接受者。

实例1:

class E {
    fun testE() {
        println("E test1")
    }
}

class F {
    fun testF() {
        println("F test1")
    }

    //创建E类的扩展函数 test2
    fun E.test2() {
        testE() //调用E的函数
        testF() //调用F的函数
    }

    fun caller(d: E) {
        d.test2()//调用扩展函数
    }
}

fun main(array: Array<String>) {
    var f = F()
    var e = E()
    f.caller(e)
}

输出:

E test1
F test1

在上面例子中,F类里面创建了E类的扩展。

此时,F被称为分发接受者。E类被称为扩展接受者

我们可以看到,在扩展函数中可以调用分发接受者的成员函数。

假如:在调用某一个函数时,该函数在分发接受者和扩展接受者中均存在。则以扩展接受者优先。要引用分发接受者的函数,可以使用限定this语句。

实例2:

class E {

    fun bar() {
        println("E.Bar")
    }
}

class F {
    fun bar() {
        println("F.Bar")
    }

    fun E.test3() {
	//将会调用E的bar方法
        bar()
	//将会调用F类,也就是当前类的bar方法
        this@F.bar()
    }

    fun test4(d: E) {
        d.test3()
    }
}

fun main(array: Array<String>) {
    var f = F()
    var e = E()
    f.test4(e)
}

输出:

E.Bar
F.Bar

以成员的形式定义的扩展函数,可以声明为open。而且可以在子类之中覆盖。

在这类扩展函数的派发过程中,针对分发接受者是虚拟的,但是针对扩展接受者任然是静态的。

实例3:

open class D1

class D2 : D1()

open class E1 {
    open fun D1.foo() {
        println("D1.foo.in E1")
    }
    open fun D2.foo(){
        println("D2.foo.in E1")
    }
    fun caller(d:D1){
        d.foo()//调用D1的扩展函数
    }
}
class E2:E1(){
    override fun D1.foo(){
        println("D1.foo.in E2")
    }
    override fun D2.foo(){
        println("D2.foo.in E2")
    }
}

fun main(array: Array<String>) {
    E1().caller(D1())
    E2().caller(D1()) //分发接受者虚拟解析。
    E1().caller(D2()) //扩展接受者静态解析。
}

输出:

D1.foo.in E1
D1.foo.in E2
D1.foo.in E1

我们可以通过继承,重构父类实现的扩展函数。

到这里,我们就大概了解了扩展的基本情况和普通场景下的使用了。

后续学习和使用之中,再加深理解。

附录
上面例子的Demo代码。
https://zinyan.com/upload/2021/07/MyClass-230930483d2b4f8e989116a6cf9efb5c.kt

0

评论区