Kotlin-面向对象-进阶

扩展

扩展方法

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
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容