Kotlin(六)之面向对象(二)

继承

  1. Kotlin的继承与java一样是单继承,即每个子类最多只能有一个父类

    修饰符 class 子类名 : 父类名 {
      
    }
    
  2. 如果在Kotlin类中没有显示指定直接父类,默认继承Any类。Any类是所有类的直接或间接父类。Any类不是java.lang.Object类,Any类只有equals()、hashcode()、toString()三个方法

  3. Kotlin类和类中的方法以及属性默认由final修饰,即默认情况下类是不能被继承的,方法也不能被覆写,属性不能覆写。如果需要,使用open关键字

  4. 子类主构造器:子类定义了主构造器,主构造器必须在继承父类的同时委托调用父类构造器

    open class Parent {
        var name: String
    
        constructor(name: String) {
            this.name = name
        }
    }
    
    /**
     * 1. 子类没有显示声明主构造器,系统会生成一个默认无参数的主构造器
     * 2. 子类默认有一个主构造器,因此要在声明继承时委托父类构造器
     */
    
    open class Child1 : Parent("jannal") {
    }
    
    /**
     * 1. 子类显示声明主构造器
     * 2. 主构造器必须在声明继承时委托调用父类构造器
     */
    open class Child2(name: String) : Parent(name) {
    }
    
  5. 子类次构造器:次构造器同样需要委托调用父类构造器。因为类的次构造器总会委托主构造器,而主构造器一定会委托调用父类构造器,因此子类的所有次构造器最终也调用了父类构造器

  6. 如果子类没有定义主构造器,此时次构造器委托调用父类构造器分为3种方式(与java构造器方式一模一样)

    • 子类构造器显示使用**:this(参数)**显示调用本类中重载的构造器
    • 子类构造器显示使用**:super(参数)**委托调用父类构造器
    • 子类构造器即没有**:super(参数)调用也没有:this(参数)**调用,系统将会在执行子类构造器之前,隐式调用父类无参数的构造器
  7. java中覆写注解@Override是可选的,Kotlin中的override修饰符是强制的。

    • 方法覆写
      • 方法名相同、形参列表相同,子类方法返回值类型应比父类方法的返回值类型更小或相等
      • 子类方法声明抛出的异常应比父类方法声明的异常类更小或相等
      • 子类方法的访问权限应比父类方法的访问权限更大或相等
      • 可以通过super来调用父类中被覆写的方法
      • 如果父类方法是private,则该方法对子类是隐藏的,子类无法访问和覆写此方法
      • 父类的方法必须由open修饰才可以覆写
    • 属性覆写
      • 父类属性必须使用open修饰,子类重写的属性必须使用override修饰
      • 对于父类的包访问权限成员变量,如果子类和父类在同一个包下, 则子类能够继承,否则子类不能继承
      • 重写的子类属性的类型与父类属性的类型要兼容
      • 重写的子类属性要提供更大的访问权限(更大或相等)。父类只读属性可以被读写属性重写,但父类读写属性不能被只读属性重写
    open class BaseClass {
        open var name: String = "jannal"
        open fun run() {
            println("BaseClass Run...")
        }
    
        private fun test() {
            println("BaseClass test...")
        }
    }
    
    open class ChildClass : BaseClass() {
    
        //覆写父类属性必须使用override
        open override var name: String = "jannal2"
    
        open override fun run() {
            //调用父类被覆写的方法
            super.run()
            //使用super访问覆写的属性
            println("ChildClass  Run... ${super.name},${this.name}")
        }
    
        //此处不是覆写,因为父类修饰符是private,可以不适用override
        fun test() {
            println("ChildClass test...")
        }
    }
    
    
    fun main() {
        var base: BaseClass = ChildClass()
        base.run()
    }
    
  8. 如果子类从多个直接超类型(接口或类)继承同名的成员,那么Kotlin要求子类必须重写该成员。如果需要在子类中使用super来引用超类型中的成员,则可使用**<>加超类型名限定super进行引用,比如super**

    open class A {
        open fun test() {
            println("A")
        }
    }
    
    
    interface B {
        fun test() {
            println("B")
        }
    }
    
    open class C : A(), B {
        //编译器强制要求必须重写test()
        override fun test() {
            super<A>.test()
            super<B>.test()
        }
    }
    

多态

  1. Ktolin与java一样,变量都有两个类型
  • 编译时类型:由声明变量时使用的类型决定
  • 运行时类型:实际赋给变量的对象决定
  1. 变量在编译阶段只能调用其编译时类型所具有的方法,但运行时则执行其运行时类型所具有的方法。如果要调用运行时类型的方法,需要做强制类型转换。Kotlin的类型转换运算符包括asas?,为了防止类型转换错误,Kotlin提供了类型检查运算符is和**!is**。Kotlin的is和**!is**非常智能,只要程序使用它们对变量进行判断,系统就会自动将变量的类型转换为目标类型

    fun main() {
        //编译时与运行时类型不一样,即发生多态,与java一样
        var base: BaseClass = SubClass()
        //输出:SubClass eat
        base.eat()
        //BaseClass run
        base.run()
        //此处是无法调用子类的runChild方法的,因为类型是BaseClass
        //base.runChild()
    
        // is会自动转换为目标类型,不需要像java一样再次强制转换
        if (base is SubClass) {
            base.runChild()
        }
    
    }
    
    open class BaseClass {
        open var name = "jannal"
        fun run() {
            println("BaseClass run")
        }
    
        open fun eat() {
            println("BaseClass eat")
        }
    }
    
    class SubClass : BaseClass() {
        open override var name = "jannal"
    
        fun runChild() {
            println("SubClass runChild")
        }
    
        open override fun eat() {
            println("SubClass eat")
        }
    
    }
    
    
  2. 强制转换运算符asas?(只能在具有继承关系的两种类型之间进行)

    • as:不安全的强制转换运算符,如果转型失败,程序会抛出ClassCastException
    • as?:安全的强制转换运算符,如果转型失败,返回null

扩展

  1. java本身不支持扩展,Kotlin支持扩展方法和属性
  2. 如果扩展定义在其他包中,一样需要使用import进行导入包
  3. 扩展方法和扩展属性的本质是以静态导入的方式实现的,为了方便管理,可以把自定义的扩展属性和扩展函数都放到单独的包中 ,作为一个通用工具类来使用

扩展方法

  1. 扩展方法定义: 函数名不要写成简单的函数,而是要在函数名前增加被扩展的类(或接口)名和点号(. )。而是要在函数名前增加被扩展类或接口名和点号

    fun main() {
        var parent = Parent()
        //调用父类的扩展方法
        parent.info();
    
        var child = Child()
        //子类对象也可以调用父类的扩展方法
        child.info()
    }
    
    
    open class Parent {
        fun add() {
            println("parent add")
        }
    }
    
    open class Child : Parent() {
        fun sub() {
            println("child sub")
        }
    }
    
    //给Parent增加扩展方法
    fun Parent.info() {
        println("扩展 info")
    }
    
  2. 扩展也可以为系统的类增加方法,为List集合扩展一个方法

    fun <T> List<T>.info() {
        println("扩展 info")
    }
    
    fun main() {
        val listOf = listOf<String>("10", "20")
        listOf.info()
    }
    
    
  3. Kotlin扩展的本质就是定义一个函数,当程序用对象调用扩展方法时,Kotlin在编译时会执行静态解析(根据调用对象、方法名找到扩展函数,转换为函数调用)。对于以下程序,如果info()是成员方法,程序就必须使用override声明子类方法重写父类方法。但由于此处采用的是扩展,因此不需要声明为方法重写。因为invokeInfo方法的形参是Base类型,Kotlin编译器对于扩展方法只会静态编译(与多态不同),所以编译器会将其替换为Base的info方法。

    open class Base
    open class Sub : Base()
    
    fun Base.info() = println("Base info")
    fun Sub.info() = println("Sub info")
    
    fun invokeInfo(base: Base) {
        base.info()
    }
    
    fun main() {
        /**
         * 由于是编译时确定,所以输出的是Base Info
         */
        invokeInfo(Sub())
    }
    
    
  4. 成员方法执行动态解析(由运行时类型决定),扩展方法执行静态解析(由编译时类型决定)。如果一个类具有相同签名的成员方法和扩展方法,成员方法的优先级高于扩展方法

  5. 为null调用扩展方法

    //为可空类型扩展equals方法
    fun Any?.equals(other: Any?): Boolean {
        if (this == null) {
            return if (other == null) true else false
        }
        return other.equals(other)
    }
    
    
    fun main() {
        var a = null
        //true
        println(a.equals(null))
        //false
        println(a.equals("Kotlin"))
    }
    

扩展属性

  1. Kotlin虽然支持扩展属性,但是由于Kotlin的扩展并不能真正的修改目标类。所以扩展属性其实就是通过添加getter和setter方法来实现的,没有幕后字段。
    • 扩展属性不能有初始值(没有存储属性值的幕后字段)
    • 不能用field关键字显示访问幕后字段
    • 扩展只读属性必须提供getter方法,扩展读写属性必须提供getter和setter方法

final与open

  1. final用于修饰类、属性、方法,表示它修饰的类、属性和方法不可改变

  2. Kotlin会为非抽象类自动添加final,也会为非抽象方法、非抽象属性等无须重写的成员自动添加final修饰符。如果需要取消Kotlin自动添加的final修饰符,使用open修饰符

  3. Kotlin与java的区别:Kotlin的final和open都不能修饰局部变量。java中使用final修饰常量,常量在编译阶段会被替换。但Kotlin不允许直接在类中定义成员变量(Kotlin定义的是属性),因此Kotlin不能使用final定义常量。

  4. Kotlin提供了const用来修饰编译时常量,常量还需要满足

    • 位于顶层或者对象表达式的成员
    • 初始值为基本类型(java的8中基本类型)或字符串字面值
    • 没有自定义的getter方法
    //编译时常量
    const val MAX_AGE = 100
    //运行时常量,编译时无法确定,此时不能使用const,否则会编译错误
    val USER = "jannal"
    val NAME: String = USER + "111"
    
    fun main() {
        println(MAX_AGE)
    }
    
  5. final类、方法和属性

    • final属性表明该属性不能被覆写
    • final方法表明该方法不能被覆写
    • final类表明该类不能被继承

抽象类

  1. 与java抽象类区别不大

    fun main() {
        /**
         * 输出
         * base init
        base constructor
        Child init
        Child constructor
        Child run
         */
        var child = Child()
        child.run()
    }
    
    abstract class AbstractBase {
        init {
            println("base init")
        }
    
        constructor() {
            println("base constructor")
        }
    
        abstract fun run(): Unit
    }
    
    class Child : AbstractBase {
    
        init {
            println("Child init")
        }
    
        constructor() {
            println("Child constructor")
        }
    
        override fun run() {
            println("Child run")
        }
    
    }
    
  2. Kotlin也允许重写父类的非抽象的方法

    open class Parent {
        open fun info(): Unit = Unit
    }
    
    abstract class Sub : Parent() {
        override abstract fun info()
    }
    
  3. 密封类:密封类是一种特殊的抽象类,密封类的子类是固定的,密封类的子类必须与密封类在同一个文件夹下,在其他文件中则不能为密封类派生子类,这就限制了在其他文件中派生子类。在Kotlin1.1之前,密封类的子类必须在密封类内部声明。

    fun main() {
        var redApple = RedApple()
        redApple.taste();
    }
    
    sealed class Apple {
        abstract fun taste()
    }
    
    open class RedApple : Apple() {
        override fun taste() {
            println("红富士苹果好吃")
        }
    
    }
    
  4. 密封类经过Kotlin编译器之后就得到一个抽象类的class文件,只不过该抽象类的普通构造器是private。而Kotlin会为之创建一个对应的带kotlin.jvm.internal.DefaultConstructorMarker参数的构造器

  5. 密封类的直接子类必须与密封类位于同一个文件中,但密封类的间接子类(子类的子类)则无须在同一个文件中。

接口

  1. kotlin的接口类似java8中的接口,接口即可以包含抽象方法,也可以包含非抽象方法。接口中的属性没有幕后字段,因此无法保存状态,所以接口中的属性要么声明为抽象属性,要么为之提供getter和setter方法

  2. 接口定义

    [修饰符] interface 接口名: 父接口1,父接口2...{
       ..属性定义..
       ..方法定义..
       ..嵌套类、嵌套接口、嵌套枚举定义..
    }
    
  3. 接口支持多继承

    interface interfaceA {
        val username: String
            get() = "jannal"
    
        fun info()
    }
    
    interface interfaceB {
        val password: String
            get() = "123456"
    
        fun run()
    }
    
    interface interfaceC : interfaceA, interfaceB {
        val age: Int
            get() = 19
    
        fun test()
    }
    
  4. 接口不能创建实例,但是可以声明变量,所有接口类型都可以直接赋值给Any类型的变量

  5. 如果要实现多个接口,并且这些接口具有相同的方法签名,那么就会存在覆写冲突,可以通过super<interfaceD>.info()的形式解决

    interface interfaceD {
        fun info() {
            println("D")
        }
    }
    
    interface interfaceE {
        fun info() {
            println("E")
        }
    }
    
    interface interfaceF : interfaceD, interfaceE {
        override fun info() {
            //编译失败
            //super.info()
            
            super<interfaceD>.info()
            super<interfaceE>.info()
        }
    }
    

嵌套类和内部类

  1. java的内部类分为:静态内部类和非静态内部类。因为Kotlin没有static修饰符,但是kotlin同样也需要静态内部类和非静态内部类,所以在Kotlin中有嵌套类(相当于静态内部类)和内部类(非静态内部类)

    • 嵌套类:只要将一个类放在另一个类中定义,这个类就变成了嵌套类
    • 内部类:使用inner修饰的嵌套类就是内部类。
  2. 内部类相当于外部类的实例成员,因此可以直接访问外部类的所有成员,如果外部类属性、方法与内部类属性、方法同名,可以使用this进行区分。内部类可以访问外部类的private属性,反之不行。

    fun main() {
        /**
         * Father info
         * Father:jannal,123,Son:jack
         * Father info
         */
        var father = Father("jannal", "123")
        father.info()
        father.sonInfo()
    }
    
    class Father(var username: String, var password: String) {
    
        private inner class Son(var username: String) {
            fun info() {
                println("Father:${this@Father.username},${this@Father.password},Son:${this.username}")
                this@Father.info()
            }
        }
    
        fun info() {
            println("Father info")
        }
    
        fun sonInfo() {
            var son = Son("jack")
            son.info()
        }
    
    }
    
  3. 嵌套类

    • 嵌套类不能访问外部类的其他任何成员,只能访问外部类的其他嵌套类
    • 嵌套类相当于外部类的静态成员,因此外部类的方法、属性、初始化块都可以使用嵌套类来定义变量、创建对象等。
    • Kotlin允许在接口中定义嵌套类,但不允许在接口中定义内部类
  4. 在外部类以外使用内部类

    class Out {
        inner class In(msg: String) {
            init {
                println(msg)
            }
    
        }
    }
    
    fun main() {
        var inVar: Out.In = Out().In("jannal")
    }
    
  5. 在外部类以外使用嵌套类,因为嵌套是属于外部类的类本身(静态内部类),因此创建时无需创建外部类对象

    class NestedOut{
        open class NestedIn{
            init {
                println("NestedIn")
            }
        }
    }
    
    fun main() {
        val nestedOut:NestedOut.NestedIn = NestedOut.NestedIn()
    }
    
    
  6. 嵌套类子类

    
    //定义一个空的嵌套类的子类
    class NestedOutSub : NestedOut.NestedIn() 
    
    
  7. 局部嵌套类:如果把一个嵌套类放在方法或函数中定义,则这个嵌套类就是一个局部嵌套类。局部嵌套类仅在该方法或函数中有效。由于局部嵌套类不能在方法或函数以外的地方使用,因此局部嵌套类也不能使用访问控制修饰符修饰。

    class LocalNestedClass {
        fun info() {
            open class NestedBase(var a: Int = 0) {}
            class NestedSub(var b: Int = 0) : NestedBase() {}
    
            val ns = NestedSub()
            ns.a = 5
            ns.b = 6
            println("${ns.a},${ns.b}")
        }
    }
    fun main() {
        LocalNestedClass().info()
    }
    
    

匿名内部类与对象表达式

  1. java中有匿名内部类。而Kotlin没有这个功能。但是Kotlin提供了一个更加强大的语法:对象表达式

  2. 匿名内部类只能指定一个父类型(接口或父类),但对象表达式可指定0~N个父类型

  3. 对象表达式

    object[:0~N个父类型]{
      
    }
    
  4. 对象表达式的规则

    • 对象表达式不能是抽象类,因为系统在创建对象表达式时会立即创建对象,因此不允许将对象表达式定义成抽象类
    • 对象表达式不能定义构造器,但是可以定义初始化块
    • 对象表达式可以包含内部类,不能包含嵌套类
    • 对象表达式可指定多个父类型
    • 对象表达式可以访问或修改其所在范围内的局部变量
  5. 案例

    fun main() {
        var obj = object : Parent {
            override fun run(msg: String) {
                println(msg)
            }
        }
        obj.run("jannal")
    
        //零个父类型的对象表达式
        var zeroObject = object {
            init {
                println("init")
            }
    
            var name = "jannal"
            fun test() {
                println("test")
            }
    
            //只能包含内部类,不能包含嵌套类
            inner class InnerClass
        }
    
        println(zeroObject.name)
        zeroObject.test()
    
        //指定多个父类型的对象表达式
        var obj3 = object : interfaceA, interfaceB {
            override fun infoA() {
                println("A")
            }
    
            override fun infoB() {
                println("B")
            }
    
        }
        obj3.infoA()
    
        obj3.infoB()
    
    }
    
    interface Parent {
        fun run(msg: String)
    }
    
    interface interfaceA {
        fun infoA()
    }
    
    interface interfaceB {
        fun infoB()
    }
    
    
    

对象声明

  1. 对象声明的语法

    object ObjectName[:0~N个父类型]
    
  2. 对象声明与对象表达式的区别就是,对象表达式在object关键字后没有名字。而对象声明需要在object关键字后指定名字。

  3. 对象表达式和对象声明的区别

    • 对象表达式是一个表达式,因此可以被赋值给变量。而对象声明不是表达式,因此不能用于赋值
    • 对象声明可包含嵌套类,不能包含内部类。而对象表达式式可以包含内部类,不能包含嵌套类
    • 对象声明不能定义在函数或方法内,对象表达式可嵌套在其他对象声明或非内部类中
  4. 对象声明专门用于实现单例模式,对象声明所定义的对象也是该类的唯一实例。程序可通过对象声明的名称直接访问该类的唯一实例

this

  1. Kotlin中的this比java的this更加强大。

    • 在类的方法或属性中,this代表调用该方法或属性的对象
    • 在类的构造器中,this代表该构造器即将返回的对象
    • 在扩展函数或带接收者的函数字面值中,this表示点(.)左边的接收者
    • 如果this没有限定符,那么它优先代表包含该this的最内层的接收者,并且会自动向外搜索。如果要让this明确引用特定的接收者,则可使用标签限定符
  2. 案例

    class A {
        inner class B {
            fun Int.foo() {
                //A的this
                val a = this@A
                //B的this
                val b = this@B
                //该方法所属对象Int对象
                val c = this
                val d = this@foo
    
                var funA = Lambda@ fun String.() {
                    val d = this
                    val d1 = this@Lambda
    
                }
                "jannal".funA()
    
                val funB = {
                    val e = this
                    val f = this@foo
    
                }
                funB()
            }
    
            fun testB() {
                2.foo()
            }
        }
    
        fun testA() {
            var b = B()
            b.testB()
        }
    }
    
    fun main() {
        var a = A()
        a.testA()
    }
    

伴生对象

  1. 在类中定义的对象声明,可使用companion修饰。这样此对象就是伴生对象

  2. 每个类最多只能定义一个伴生对象,伴生对象相当于外部类的对象,程序可通过外部类直接调用伴生对象的成员。

  3. Kotlin取消了static关键字,因此Koltin引入了伴生对象来弥补没有静态成员的不足。伴生对象的主要作用就是为其所在的外部类模拟静态成员。伴生对象的成员依然是伴生对象本身的实例成员,并不属于伴生对象所在的外部类

    fun main() {
    
        ClassA.infoC("jannal")
        println(ClassA.username)
    }
    
    interface interfaceC {
        fun infoC(msg: String)
    }
    
    class ClassA {
        companion object A : interfaceC {
            var username = "jannal"
            override fun infoC(msg: String) {
                println(msg)
            }
    
        }
    }
    
  4. 在JVM平台上可通过@JvmStatic注解让系统根据伴生对象的成员为其所在的外部类生成真正的静态成员

    class ClassB {
        companion object B {
            @JvmStatic
            var password = "123"
        }
    }
    fun main() {
        println(ClassB.password)
    }
    
    
  5. 伴生对象也可以被扩展。如果一个类具有伴生对象,则Kotlin允许为伴生对象扩展方法和属性。为伴生对象扩展的方法和属性,就相当于为伴生对象所在的外部类扩展了静态成员,可通过外部类的类名访问这些扩展成员。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值