美文网首页
Kotlin-面向对象-进阶

Kotlin-面向对象-进阶

作者: AilurusFulgens | 来源:发表于2020-11-10 21:15 被阅读0次

    扩展

    扩展方法

    Kotlin支持扩展方法和扩展属性。语法:被扩展的类/接口名.方法名()

    open class ExtensionTest {
        fun test() = println("--test()--")
    }
    
    class SubExtensionTest: ExtensionTest() {
        fun subTest() = println("--subTest()--")
    }
    
    fun ExtensionTest.infoEx() = println("--扩展ExtensionTest infoEx()--")
    fun SubExtensionTest.infoSubEx() = println("--扩展SubExtensionTest infoSubEx()--")
    
    val extensionTest = ExtensionTest()
    extensionTest.test() //--test()--
    val subExtensionTest = SubExtensionTest()
    subExtensionTest.subTest() //--subTest()--
    extensionTest.infoEx() //--扩展ExtensionTest infoEx()--
    subExtensionTest.infoSubEx() //--扩展SubExtensionTest infoSubEx()--
    // extensionTest.infoSubEx() //父类不能使用子类的扩展方法
    subExtensionTest.infoEx() //子类可以使用父类的扩展方法 --扩展ExtensionTest infoEx()--
    

    父类不能使用子类的扩展方法
    子类可以使用父类的扩展方法

    当然,你也可以扩展系统的类,比如IntList等。

    为可空类型扩展方法

    由于可空类型允许接收null值,这样使得null值也可调用该扩展方法。

    fun Any?.equals(other: Any?): Boolean = this?.equals(other) ?: (other == null)
    
    val a = null
    println(a.equals(null)) //true
    println(a.equals("")) //false
    
    扩展属性

    由于Kotlin的扩展并不能真正修改目标类,因此扩展属性其实是通过添加gettersetter方法实现的,没有幕后字段。简单来说,扩展属性只能是计算属性。

    有两个限制:
    1. 扩展属性不能有初始值,也不能有 field 关键字显式访问幕后字段(因为没有存储属性值的幕后字段)
    2. 扩展只读属性必须提供 getter 方法;扩展读写属性必须提供 getter 和 setter 方法。

    class ExtensionProps(var first: String, var last: String)
    // 扩展属性fullName
    val ExtensionProps.fullName: String
        get() = first + last
    
    val extensionProps = ExtensionProps("a", "b")
    println("first: ${extensionProps.first}, last: ${extensionProps.last}, fullName: ${extensionProps.fullName}") //first: a, last: b, fullName: ab
    

    还可使用泛型函数的形式来扩展属性。

    // 使用泛型函数形式来扩展属性,扩展系统类List的lastIndex属性
    val <T> List<T>.lastIndex: Int
        get() = size - 1
    
    val lastIndex = listOf("1", "2", "3").lastIndex
    println(lastIndex) //2
    
    以成员方式进行扩展

    扩展不仅可以定义在顶层函数中,还可以定义在类成员中,这样它就可以直接调用被扩展类的成员和所在类的成员(可以省略this)。

    class A {
        fun a() = println("--a--")
    }
    
    class B {
        fun b() = println("--b--")
    
        fun A.extensionA() {
            // 可以使用扩展类中的方法
            a() //--a--
            // 可以使用所在类中的方法
            b() //--b--
        }
    }
    

    如果被扩展的类和所在类存在同名方法,那么系统总是会优先调用被扩展类的方法,如果需要调用所在类的方法,需要加上带标签的this进行限定

    class A {
        fun test() = println("--a test--")
    }
    
    class B {
        fun test() = println("--b test--")
    
        fun A.extensionA() {
            // 扩展方法中,默认调用扩展类中的方法
            test() //--a test--
            // 可以使用this@类名的方式制定调用所在类的方法
            this@B.test() //--b test--
        }
    }
    
    带接受者的匿名函数

    Kotlin支持为类扩展匿名函数,本质上是扩展了一个函数类型的参数。

    // 为Int扩展普通函数
    fun Int.name(multiple: Int): Int = this * multiple
    
    // 定义一个带接收者的匿名函数,相当于为Int扩展了匿名函数
    val noNameFun = fun Int.(multiple: Int): Int = this * multiple
    
    // 这个是调用Int的name方法
    println(3.name(2)) //6
    // noNameFun的类型是:Int.() -> Int / Function2<Int, Int, Int>
    println(4.noNameFun(3)) //12
    println(noNameFun.invoke(4, 3)) //12,与上面等价
    

    如果接收者类型可以通过上下文推断出来,那么Kotlin允许使用Lambda表达式作为带接收者的匿名函数。

    class HTML {
        fun head() = println("  <head></head>")
        fun body() = println("  <body></body>")
    }
    
    // init: HTML.() -> Unit,相当于为HTML类扩展了一个类型是 () -> Unit 的 init 函数
    fun html(init: HTML.() -> Unit) {
        println("<html>")
        val html = HTML()
        // 调用扩展函数 init
        html.init()
        println("</html>")
    }
    
    // 调用html函数,可接受一个lambda表达式作为参数,this(可省略)代表该方法的调用者。
    html {
        head()
        body()
    }
    // <html>
    //   <head></head>
    //   <body></body>
    // </html>
    

    final 和 open

    final关键字可用于修饰类,属性和方法,表示这些不可改变。Kotlin会为非抽象的类,属性和方法自动添加final,若你想取消final,则可用open修饰。
    finalopen无法修饰局部变量。

    编译时常量 const val

    在Java中,经常会这么写:
    public static final String TAG = "tag";
    当程序使用TAG这个常量时,编译器会自动将TAG的值放到对应的调用处,所以对于程序来说,TAG根本不存在。
    由于final在Kotlin中不允许修饰局部变量,所以Kotlin提供了const来修饰编译时常量。
    有以下3点需要注意:

    1. 位于顶层或者是对象表达式的成员;
    2. 初始值为基本数据类型(Java的8中基本类型)或字符串字面值;
    3. 没有自定义getter方法。
    const val TAG = "tag"
    fun main() {
        println(TAG) //tag
    }
    

    抽象类 abstract

    抽象成员(方法和属性)以及抽象类需要使用abstract修饰。
    包含抽象成员的类只能定义成抽象类,抽象类中可以没有抽象成员。

    1. 抽象类和抽象成员必须使用abstract修饰,抽象方法不能有方法体;
    2. 抽象类不能被实例化,其构造器只能用于被子类调用;
    3. 抽象类中可以包含属性,方法(普通方法、抽象方法),构造器,初始化块和嵌套类(接口、枚举);
    4. 含有抽象成员的类,或者没有完全实现父类的抽象成员,或者没有完全实现接口包含的抽象成员的类只能被定义成抽象类。
    abstract class Shape() {
        init {
            println("Shape init")
        }
    
        //定义抽象属性:类型
        abstract val type: String
    
        //定义抽象方法,计算周长
        abstract fun calPerimeter(): Double
    
        var color = ""
        constructor(color: String): this() {
            println("带color参数的构造器")
            this.color = color
        }
    }
    
    class Triangle(color: String, private var a: Double, private var b: Double, private var c: Double): Shape(color) {
    
        override val type: String = "三角形"
    
        override fun calPerimeter(): Double = a + b + c
    }
    
    val triangle = Triangle("red", 3.0, 4.0, 5.0)
    println("${triangle.type}的周长是:${triangle.calPerimeter()},颜色是:${triangle.color}") //三角形的周长是:12.0,颜色是:red
    

    密封类 sealed class

    密封类是一种特殊的抽象类,专门用于派生子类。密封类用于子类类型有限的类。

    密封类与普通抽象类的区别在于:
    密封类的子类是固定的,密封类的子类必须与密封类本身在同一个文件中。
    但是密封类的子类的子类无需在同一个文件中。

    密封类的所有构造都必须是private,无论开发者是否使用private修饰,系统都会为之添加private修饰。

    // 定义密封类,在类中定义抽象方法,所以密封类本质就是抽象类
    sealed class Apple {
        abstract fun taste()
    }
    
    class RedFuji: Apple() {
        override fun taste() {
            println("红富士苹果香甜可口")
        }
    }
    
    class Gala: Apple() {
        override fun taste() {
            println("嘎啦果更清脆")
        }
    }
    
    // 使用密封类的好处是:编译器可以明确知道子类个数,因为使用when表达式,不需要添加else语句
    fun judge(apple: Apple) {
        when (apple) {
            is RedFuji -> println("红富士苹果")
            is Gala -> println("嘎啦果")
        }
    }
    
    val redFuji = RedFuji()
    redFuji.taste() //红富士苹果香甜可口
    val gala = Gala()
    gala.taste() //嘎啦果更清脆
    judge(redFuji) //红富士苹果
    judge(gala) //嘎啦果
    

    接口

    1. 接口中定义的方法即可以是抽象方法,也可以是非抽象方法。
    2. 如果一个方法没有方法体,会自动添加abstract修饰符,即抽象方法。
    3. 如果一个val属性没有定义getter,会自动添加abstract修饰符,即抽象属性。
    4. 如果一个var属性没有定义gettersetter,会自动添加abstract修饰符,即抽象属性。

    Kotlin接口中的成员可以支持privatepublic两种访问权限,具体规则如下:

    • 抽象属性和抽象方法只能是public,默认为public且只能使用public修饰。
    • 非抽象属性和非抽象方法可以使用privatepublic,默认为public
    interface InterfaceTest {
        // 只读属性没有定义getter方法,抽象属性,默认为public,且只能为public
        val name1: String
        // 读写属性没有定义getter、setter方法,抽象属性,默认为public,且只能为public
        var name2: String
        // 只读属性定义了getter方法,非抽象属性,默认为public
        val name3: String
            get() = "name3"
        // 只读属性定义了getter方法,非抽象属性,使用private修饰
        private val name4: String
            get() = "4"
    
        // 没有方法体,抽象方法
        fun test1()
        // 有方法体,非抽象方法,默认为public
        fun test2() {
            println("---InterfaceTest test2---")
        }
        // 有方法体,非抽象方法,使用private修饰
        private fun test3() {
            println("---InterfaceTest test3---")
        }
    }
    

    嵌套类和内部类

    • 嵌套类(相当于Java静态内部类):只要将一个类放在另一个了中的定义,那这个类就是嵌套类。
    • 内部类(相当于Java非静态内部类):使用inner修饰的嵌套类叫做内部类。
    嵌套类

    等同于 Java 的静态内部类,因此嵌套类直接属于外部类的类本身,而不是外部类的实例成员。

    Java 语法有一条规则:静态成员不可访问非静态成员,而 Kotlin 取消了static关键字,因此 Kotlin 类中的成员除了嵌套类之外都是非静态成员。
    因此:嵌套类不能访问外部类的其他成员,只能访问另一个嵌套类。

    class OuterClass1 {
    
        class NestedClass1 {
            fun nestedInfo1() {
                println("--nested1 info--")
            }
    
            fun nestedTest1() {
                NestedClass2().nestedInfo2()
                // outerTest1() 无法直接调用外部类的方法
            }
        }
    
        class NestedClass2 {
            fun nestedInfo2() {
                println("--nested2 info--")
            }
    
            fun nestedTest2() {
                NestedClass1().nestedInfo1()
                // outerTest1() 无法直接调用外部类的方法
            }
        }
    
        fun outerTest1() {
            println("--outer test1--")
            NestedClass1().nestedTest1()
            NestedClass2().nestedTest2()
        }
    }
    
    val outerClass1 = OuterClass1()
    outerClass1.outerTest1() //--outer test1-- / --nested2 info-- / --nested1 info--
    
    内部类

    需要使用inner修饰,等同于Java的非静态内部类,相当于外部类的实例成员,因此他可以直接访问外部类的所有成员。

    1. 内部类可使用外部类的属性和方法,包括被private修饰的属性和方法;
    2. 外部类使用内部类的属性和方法时,需要显式创建内部类对象,且无法使用private修饰的属性和方法;
    3. 可以使用this@限定符来访问指定类的属性和方法。
    // 定义外部类
    class OuterClass {
        //定义内部类,使用inner修饰
        inner class InnerClass {
            //定义一个内部类的 public 方法
            fun innerInfo() {
                println("--inner fun info--")
            }
    
            //定义一个内部类的 private 方法
            private fun innerPrivateInfo() {
                println("--inner private fun info--")
            }
    
            //定义一个 innerTest 方法
            fun innerTest() {
                // 内部类可直接使用外部类的方法
                outerInfo()
                // 内部类可直接使用外部类的方法,包括 private 方法
                outerPrivateInfo()
                // 可以使用this限定符,来访问指定类的属性或方法
                this@OuterClass.outerTest()
            }
        }
    
        //定义一个外部类的 public 方法
        fun outerInfo() {
            println("--outer fun info--")
        }
    
        //定义一个外部类的 private 方法
        private fun outerPrivateInfo() {
            println("--outer fun private info--")
        }
    
        //定义一个 outerTest 方法
        fun outerTest() {
            val innerClass = InnerClass()
            // 外部类需要使用内部类的方法,需要显式创建内部类的对象
            innerClass.innerInfo()
            // 外部类无法调用内部类的 private 属性或方法
            // innerClass.innerPrivateInfo()
        }
    }
    
    val outerClass = OuterClass()
    outerClass.outerTest() //--inner fun info--
    val innerClass = outerClass.InnerClass()
    innerClass.innerTest() //--outer fun info-- / --outer fun private info-- / --inner fun info--
    
    在外部类以外使用嵌套类

    由于嵌套类直接属于外部类的类本身,所以想要创建嵌套类的对象,无需先创建外部类对象:
    Outer.Inner()

    在外部类以外使用内部类

    由于内部类相当于外部类的实例成员,所以想要创建内部类的对象,必须先创建外部类对象:
    Outer().Inner()

    局部嵌套类

    如果把一个嵌套类放在方法或函数中定义,那这个类就是一个局部嵌套类,该类仅在该方法或函数中有效,故也不能使用访问控制修饰符。

    局部嵌套类是一个非常“鸡肋”的语法,一般不常用。

    匿名内部类

    Java中有一个非常实用的功能:匿名内部类,Kotlin则彻底抛弃了这个功能,但是Kotlin提供了一个更加强大的语法:对象表达式(其实就是增强版的匿名内部类)。


    对象表达式和对象声明

    对象表达式

    Kotlin的对象表达式比Java的匿名内部类更强大,区别在于:
    匿名内部类只能指定一个父类型(接口或父类)
    对象表达式可指定0 ~ N个父类型(接口或父类)

    对象表达式的语法格式:
    object : [0~N个父类型] { ... }

    对象表达式有下列规则:

    1. 对象表达式不能是抽象类,因为和匿名内部类一样,编译器会立即创建对象;
    2. 对象表达式不能定义构造器,但允许定义初始化块;
    3. 对象表达式可以包含内部类(inner),但是不能包含嵌套类。
    interface IObject {
        fun iTest(msg: String)
    }
    
    abstract class AObject(var msg: String) {
        abstract val name: String
        abstract fun aTest()
    }
    
    fun main() {
        // 指定2个父类型
        val objTest = object : IObject, AObject("a object msg") {
            override fun iTest(msg: String) {
                println("IObject iTest $msg")
            }
    
            override val name: String
                get() = "a object name"
    
            override fun aTest() {
                println("IObject iTest $msg")
            }
        }
        objTest.iTest("123") //IObject iTest 123
        println(objTest.name) //a object name
        objTest.aTest() //IObject iTest a object msg
    
        // 指定0个父类型
        val objTest2 = object {
            // 可以定义初始化代码块
            init {
                println("objTest2 init")
            }
    
            fun test(msg: String) {
                println("objTest2 test $msg")
            }
    
            // 这里只能定义内部类,不能定义嵌套类
            inner class InnerClass {}
        }
        objTest2.test("321") //objTest2 init / objTest2 test 321
    }
    
    对象声明和单例模式

    对象声明的语法格式:
    object ObjectName : [0~N个父类型] { ... }

    对象声明专门用于实现单例模式(饿汉式)

    对象声明和对象表达式的区别:

    1. 对象表达式没有名字,对象声明要指定名字;
    2. 对象表达式是一个表达式,因此可以赋值给变量;而对象声明不是表达式,因此不能用于赋值;
    3. 对象表达式可包含内部类,不能包含嵌套类;对象声明可包含嵌套类,不能包含内部类;
    4. 对象声明不能定义在函数和方法内;但对象表达式可以嵌套在其他对象或非内部类中。
    interface IObject {
        fun iTest(msg: String)
    }
    
    abstract class AObject(var msg: String) {
        abstract val name: String
        abstract fun aTest()
    }
    
    object MyObj : IObject, AObject("aaa") {
    
        override fun iTest(msg: String) {
            println("--MyObj iTest $msg--")
        }
    
        override val name: String
            get() = "MyObj name"
    
        override fun aTest() {
            println("--MyObj aTest $msg--")
        }
    
        // 这里只能定义嵌套类,不能定义内部类
        class NestedClass
    }
    
    fun main() {
        MyObj.iTest("bbb") //--MyObj iTest bbb--
        println(MyObj.name) //MyObj name
        MyObj.aTest() //--MyObj aTest aaa--
        println(MyObj.msg) //aaa
    }
    
    伴生对象和静态成员

    在类中定义的对象声明,可使用companion修饰,这样该对象就变成了伴生对象。

    1. 伴生对象的名字可以省略,因为它相当于外部类的对象,可以使用Companion进行访问;
    2. Kotlin取消了static关键字,因此Kotlin引入了伴生对象来弥补没有静态成员的不足,用来为其所在的外部类模拟静态成员。
    3. 在JVM平台上,可添加@JvmStatic注解让系统根据伴生对象的成员为其所在的外部类生成真正的静态成员。
    class CompanionClassTest {
        companion object {
            fun test() {
                println("--companion test--")
            }
        }
    }
    
    fun main() {
        CompanionClassTest.test() //--companion test--
    }
    
    伴生对象的扩展

    伴生对象也支持扩展字段和属性,需要使用到Companion指定伴生类。

    class CompanionClassTest {
        companion object {
            fun test() {
                println("--companion test--")
            }
        }
    }
    
    // 为CompanionClassTest类的伴生对象扩展属性
    fun CompanionClassTest.Companion.info() {
        println("--companion info--")
    }
    
    fun main() {
        CompanionClassTest.info() //--companion info--
    }
    

    枚举类

    使用enum class来定义枚举类。
    枚举类和普通类的区别:

    1. 枚举类可以实现一个或多个接口,默认继承kotlin.Enum,而不是Any,因此枚举类不能显式继承其他父类;枚举类还实现了kotlin.Comparable接口;
    2. 枚举类不能使用open修饰,因此不能派生子类;
    3. 枚举类的构造器只能使用private修饰,默认private
    4. 枚举类的所有实例必须在第一行显式列出,且最后要用;结尾;

    常用的方法:

    1. EnumClass.valueOf(value: String): EnumClass
      public inline fun <reified T : Enum<T>> enumValueOf(name: String): T
      根据value字符串获取实际的枚举值,若无匹配,会抛出IllegalArgumentException
    2. EnumClass.values(): Array<EnumClass>
      public inline fun <reified T : Enum<T>> enumValues(): Array<T>
      获取所有枚举值组成的数组;
    3. name:返回此枚举实例的名称,也可使用toString()
    4. ordinal:返回此枚举实例的索引;
    5. public override final fun compareTo(other: E): Int
      比较顺序,在之后返回正数,在之前返回负数,否则返回0;
    6. toString():与name类似。
    enum class Season(private val desc: String): SeasonDesc {
        SPRING("春季"), SUMMER("夏季"), AUTUMN("秋季"), WINTER("冬季");
    
        override fun info() {
            when(this) {
                SPRING -> println("这是$desc")
                SUMMER -> println("这是$desc")
                AUTUMN -> println("这是$desc")
                WINTER -> println("这是$desc")
            }
        }
    }
    
    interface SeasonDesc {
        fun info()
    }
    
    fun main() {
        println(Season.valueOf("SPRING")) //SPRING
        println(enumValueOf<Season>("SUMMER")) //SUMMER
        Season.values().forEach {
            println(it)
        }
        enumValues<Season>().forEach {
            println(it)
        }
        // 获取枚举值的名字
        println(Season.SUMMER.name) //SUMMER
        // 获取枚举值的索引
        println(Season.AUTUMN.ordinal) //2
        // 比较顺序,在之后返回正数,在之前返回负数,否则返回0
        println(Season.WINTER.compareTo(Season.AUTUMN)) //1
        Season.SPRING.info() //这是春季
    }
    
    包含抽象方法的抽象枚举类
    1. 包含抽象方法的枚举类为抽象枚举类,但是不能使用abstract修饰;
    2. 每个枚举都必须提供对应抽象方法的实现。
    // 包含抽象方法的枚举类为抽象枚举类,但是不能使用abstract修饰(系统会自己添加)
    enum class Operation {
    
        PLUS {
            override fun eval(x: Double, y: Double) = x + y
        },
        MINUS {
            override fun eval(x: Double, y: Double) = x - y
        },
        TIMES {
            override fun eval(x: Double, y: Double) = x * y
        },
        DIVIDE {
            override fun eval(x: Double, y: Double) = x / y
        };
    
        // 为枚举类提供抽象方法,需要每个枚举提供对应的实现
        abstract fun eval(x: Double, y: Double): Double
    }
    
    fun main() {
        println(Operation.PLUS.eval(4.0, 3.0)) //7.0
        println(Operation.MINUS.eval(4.0, 3.0)) //1.0
        println(Operation.TIMES.eval(4.0, 3.0)) //12.0
        println(Operation.DIVIDE.eval(4.0, 3.0)) //1.3333333333333333
    }
    

    类委托和属性委托

    类委托

    将本类需要实现的部分方法委托给其他对象,相当于借用其他对象的方法作为自己的实现。

    interface IDelegate {
        fun test(msg: String)
        var type: String
    }
    
    class DefaultDelegate : IDelegate {
    
        override fun test(msg: String) {
            println("--default test: $msg--")
        }
    
        override var type: String = "默认代理类"
    }
    
    class Test1(private val delegate: DefaultDelegate) : IDelegate by delegate
    
    class Test2 : IDelegate by DefaultDelegate() {
        // 重写test方法
        override fun test(msg: String) {
            println("--Test2 test: $msg--")
        }
    }
    
    fun main() {
        val test1 = Test1(DefaultDelegate())
        // 调用委托类的字段
        println(test1.type) //默认代理类
        // 调用委托类的test方法
        test1.test("111") //--default test: 111--
        val test2 = Test2()
        // 调用委托类的字段
        println(test2.type) //默认代理类
        // 由于Test2重写了test方法,所以此处调用Test2的test方法
        test2.test("222") //--Test2 test: 222--
    }
    

    一般来说建议使用Test1的写法,通过构造器参数指定委托对象,因为这样可以让多个对象共享同一个委托对象。

    属性委托

    属性委托可以将多个类的类似属性统一交给委托对象统一实现,这样就可避免为每个类单独实现这些属性。
    由于属性是被委托的,所以不能为属性提供gettersetter方法,Kotlin也不会默认实现gettersetter方法。

    注意:
    对于val属性,需要实现ReadOnlyProperty接口,并重写getValue方法;
    对于var属性,需要实现ReadWriteProperty接口,并重写getValue和setValue方法。
    当然,也可以不实现上述接口,只需按照规定实现对应的方法即可。

    class PropertyDelegate {
        // 属性委托,交给ValPropertyDelegate委托类
        val name1: String by ValPropertyDelegate()
        // 属性委托,交给VarPropertyDelegate委托类
        var name2: String by VarPropertyDelegate()
    }
    
    // 属性委托的委托类,val属性需要实现ReadOnlyProperty接口,并重写getValue方法
    class ValPropertyDelegate: ReadOnlyProperty<PropertyDelegate, String> {
        override fun getValue(thisRef: PropertyDelegate, property: KProperty<*>): String = "val 默认值"
    }
    
    // 属性委托的委托类,var属性需要实现ReadWriteProperty接口,并重写getValue和setValue方法
    class VarPropertyDelegate: ReadWriteProperty<PropertyDelegate, String> {
        private var _backProperty = "var 默认值"
        override fun getValue(thisRef: PropertyDelegate, property: KProperty<*>): String  = _backProperty
    
        override fun setValue(thisRef: PropertyDelegate, property: KProperty<*>, value: String) {
            _backProperty = value
        }
    }
    
    fun main() {
        val propertyDelegate = PropertyDelegate()
        // 获取name1的值,其实是委托了ValPropertyDelegate的getValue方法获取到的
        println(propertyDelegate.name1) //val 默认值
        // 获取name2的值,其实是委托了VarPropertyDelegate的getValue方法获取到的
        println(propertyDelegate.name2) //var 默认值
        // 设置name2的值,其实是委托了VarPropertyDelegate的setValue方法设置的
        propertyDelegate.name2 = "新值"
        // 获取name2的值,其实是委托了VarPropertyDelegate的getValue方法获取到的
        println(propertyDelegate.name2) //新值
    }
    
    延迟属性

    Kotlin提供了一个lazy()函数,该函数接受一个Lambda表达式作为参数,并返回一个Lazy<T>对象。
    Lazy<T>对象包含了一个符合只读属性委托要求的getValue()方法,因此Lazy<T>对象只能作为只读属性的委托对象
    Lazy<T>getValue()的逻辑是:当第一次调用该方法时,会计算Lambda表达式,并得到其返回值,以后再调用该方法时,不再计算Lambda表达式,而是直接使用第一次计算得到的返回值。

    val lazyProperty: String by lazy {
        println("--第一次执行--")
        "123"
    }
    
    fun main() {
        println(lazyProperty) //--第一次执行-- / 123
        println(lazyProperty) //123
    }
    

    lazy()函数有两个版本:
    fun <T> lazy(initializer: () -> T): Lazy<T>
    fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T>
    上面代码就是第一个函数(默认mode = SYNCHRONIZED)。
    第二个函数中,多了个LazyThreadSafetyMode参数,共有三个候选值:
    SYNCHRONIZED:会添加线程安全的同步锁,开销较大,第一个函数的默认值就是它;
    PUBLICATION:多个线程可以同时执行初始化操作,但只有第一个返回的值将被用作该实例;
    NONE:没有任何线程安全的相关操作与开销,不建议在多线程环境中使用。

    val lazyProperty: String by lazy(LazyThreadSafetyMode.NONE) {
        println("--第一次执行--")
        "123"
    }
    
    fun main() {
        println(lazyProperty) //--第一次执行-- / 123
        println(lazyProperty) //123
    }
    
    属性监听

    Kotlin中的属性监听可以通过属性委托机制来实现;
    Java中的属性监听只能通过setter方法来实现;

    inline fun <T> observable(initialValue: T, crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Unit): ReadWriteProperty<Any?, T>
    返回ReadWriteProperty,所以可用作读写属性的属性委托。
    第一个参数:初始值;
    第二个参数:Lambda表达式,当设置新值时,该Lambda表达式就会执行。

    inline fun <T> vetoable(initialValue: T, crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Boolean): ReadWriteProperty<Any?, T>
    返回ReadWriteProperty,所以可用作读写属性的属性委托。
    第一个参数:初始值;
    第二个参数:Lambda表达式,当设置新值时,该Lambda表达式就会执行并返回true(新值设置成功)false(新值设置失败)

    var observableProperty: String by Delegates.observable("默认值") { _, oldValue, newValue ->
        println("$oldValue 被改为 $newValue")
    }
    // vetoable:只要该属性被重新设置值,Lambda表达式就会被执行,返回Boolean,true代表设置成功,false代表设置失败
    var vetoableProperty: Int by Delegates.vetoable(20) { _, oldValue, newValue ->
        println("$oldValue 被改为 $newValue")
        newValue > oldValue
    }
    
    fun main() {
        println(observableProperty) //默认值
        observableProperty = "新值" //默认值 被改为 新值
        println(observableProperty) //新值
        println(vetoableProperty) //20
        vetoableProperty = 10 //20 被改为 10,设置失败
        println(vetoableProperty) //20
        vetoableProperty = 30 //20 被改为 30,设置成功
        println(vetoableProperty) //30
    }
    
    使用Map存储属性值

    Map提供了一个方法:
    inline operator fun <V, V1 : V> Map<in String, @Exact V>.getValue(thisRef: Any?, property: KProperty<*>): V1
    该方法符合只读属性的委托对象的要求,即Map对象可作为只读对象的委托;

    MutableMap提供了两个方法:
    inline operator fun <V, V1 : V> MutableMap<in String, out @Exact V>.getValue(thisRef: Any?, property: KProperty<*>): V1
    inline operator fun <V> MutableMap<in String, in V>.setValue(thisRef: Any?, property: KProperty<*>, value: V)
    上面两个方法符合读写属性的委托对象的要求,即MutableMap对象可作为读写对象的委托;

    class MapDelegate(map: Map<String, Any?>) {
        val v1: String by map //属性名v1相当于map的key,属性值相当于key对应的value
        val v2: String by map //属性名v2相当于map的key,属性值相当于key对应的value
        val v3: String by map //属性名v3相当于map的key,属性值相当于key对应的value
    }
    
    class MutableMapDelegate(map: MutableMap<String, Any?>) {
        var v1: String by map //属性名v1相当于map的key,属性值相当于key对应的value
        var v2: String by map //属性名v2相当于map的key,属性值相当于key对应的value
        var v3: String by map //属性名v3相当于map的key,属性值相当于key对应的value
    }
    
    fun main() {
        val mapDelegate = MapDelegate(
            mapOf(
                "v1" to "111",
                "v2" to "222",
                "v3" to "333"
            )
        )
        println(mapDelegate.v1) //111
        println(mapDelegate.v2) //222
        println(mapDelegate.v3) //333
        val mutableMapDelegate = MutableMapDelegate(
            mutableMapOf(
                "v1" to "11111",
                "v2" to "22222",
                "v3" to "33333"
            )
        )
        println(mutableMapDelegate.v1) //11111
        mutableMapDelegate.v2 = "222222"
        println(mutableMapDelegate.v2) //222222
        println(mutableMapDelegate.v3) //33333
    }
    
    局部属性委托

    与属性委托一样,只是作用于局部变量而已。
    和属性委托的区别,thisRef 是 Nothing? 类型,因为局部变量不属于任何对象

    class LocalPropertyDelegate {
        private var localProperty: String = "局部变量初始值"
        operator fun getValue(thisRef: Nothing?, property: KProperty<*>): String {
            println("委托的 getValue")
            return localProperty
        }
    
        operator fun setValue(thisRef: Nothing?, property: KProperty<*>, value: String) {
            println("委托的 setValue")
            localProperty = value
        }
    }
    
    fun main() {
        var localDelegate: String by LocalPropertyDelegate()
        println(localDelegate) //委托的 getValue / 局部变量初始值
        localDelegate = "局部变量新值" //委托的 setValue
        println(localDelegate) //委托的 getValue / 局部变量新值
    }
    

    lazy()函数也可以对局部变量延迟初始化。

    fun main() {
        val localLazy: String by lazy {
            println("第一次执行的代码")
            "localLazy 值"
        }
        println(localLazy) //第一次执行的代码 / localLazy 值
        println(localLazy) //localLazy 值
    }
    
    委托工厂

    除提供getValue()setValue()方法的对象可作为属性的委托对象外,Kotlin还提供了一种类似“委托工厂”的对象也可作为委托对象。委托工厂需要提供如下方法:
    operator fun provideDelegate(thisRef: Any?, property: KProperty<*>): ReadOnlyProperty / ReadWriteProperty
    若返回ReadOnlyProperty,那么该对象只能作为只读属性的委托对象;
    若返回ReadWriteProperty,那么该对象就可作为读写属性的委托对象。
    Kotlin 1.4 提供了一个接口,重写里面的方法(就是上面的provideDelegate)即可:

    @SinceKotlin("1.4")
    public fun interface PropertyDelegateProvider<in T, out D> {
        public operator fun provideDelegate(thisRef: T, property: KProperty<*>): D
    }
    

    使用委托工厂的好处是,你可以添加自定义逻辑,下面代码就通过了一个委托工厂,分别返回不同的委托对象。

    class ProviderProperty {
        var provider1: String by ProviderPropertyChecker()
        var provider2: String by ProviderPropertyChecker()
    }
    
    class ProviderPropertyChecker: PropertyDelegateProvider<ProviderProperty, ReadWriteProperty<ProviderProperty, String>> {
        override fun provideDelegate(thisRef: ProviderProperty, property: KProperty<*>): ReadWriteProperty<ProviderProperty, String> {
            // 执行自定义代码,业务逻辑
            // 可通过不同属性名称,返回不同属性委托对象,这就是工厂模式
            return when (property.name) {
                "provider1" -> Provider1PropertyDelegate()
                "provider2" -> Provider2PropertyDelegate()
                else -> throw IllegalArgumentException("property name not valid!")
            }
        }
    }
    
    class Provider1PropertyDelegate: ReadWriteProperty<ProviderProperty, String> {
        private var _back = "初始值1"
        override fun setValue(thisRef: ProviderProperty, property: KProperty<*>, value: String) {
            _back = value
        }
    
        override fun getValue(thisRef: ProviderProperty, property: KProperty<*>) = _back
    }
    
    class Provider2PropertyDelegate: ReadWriteProperty<ProviderProperty, String> {
        private var _back = "初始值2"
        override fun setValue(thisRef: ProviderProperty, property: KProperty<*>, value: String) {
            _back = value
        }
    
        override fun getValue(thisRef: ProviderProperty, property: KProperty<*>) = _back
    }
    
    fun main() {
        val providerProperty = ProviderProperty()
        println(providerProperty.provider1) //初始值1
        println(providerProperty.provider2) //初始值2
        providerProperty.provider1 = "新值1"
        println(providerProperty.provider1) //新值1
        providerProperty.provider2 = "新值2"
        println(providerProperty.provider2) //新值2
    }
    

    相关文章

      网友评论

          本文标题:Kotlin-面向对象-进阶

          本文链接:https://www.haomeiwen.com/subject/ptzovktx.html