天天看點

瘋狂Kotlin講義學習筆記08章:面向對象(下)嵌套類、内部類、局部嵌套類,匿名内部類,對象表達式,對象聲明,伴生對象,枚舉類,類委托,屬性委托,延遲屬性,屬性監聽,map存儲屬性,委托工廠

20、嵌套類和内部類概述

類定義在一個類的内部叫做嵌套類

内部類分靜态内部類(static修飾)和非靜态内部類(無static修飾)

嵌套類(相當于靜态内部類):放在一個類的内部,用static修飾

内部類(非靜态内部類):使用inner修飾,相當于java中的無static修飾非靜态内部類

隻需要把一個類放在另一個類的類内部嵌套定義即可,甚至可以在方法中定義嵌套類

21、内部類

//通過主構造器為外部類定義屬性
class Cow(var weight:Double=0.0){
    //定義一個内部類(用inner修飾,相當于java的非靜态内部類)
    //通過主構造器為内部定義屬性
    private inner class Cowleg(var length:Double=0.0,var cololr:String=""){
        //内部類的方法
        fun info(){
            println("目前牛腿顔色是:${cololr},高:${length}")
            //直接通路外部類的private修飾的foo()方法
            foo()
        }
    }

    fun test(){
        val c1=Cowleg(1.12,"黑白相間")
        c1.info()
    }

    private fun foo() {
        println("Cow的foo方法")
    }
}

fun main(args:Array<String>){
    val cow=Cow(378.9)
    cow.test()
}
           

列印:

目前牛腿顔色是:黑白相間,高:1.12

Cow的foo方法

如果外部類屬性、内部類屬性與内部類中方法的局部變量同名,則可通過使用this、帶标簽的[email protected](通路外部類屬性)來進行限定區分

關于this,kotlin的處理規則如下

  • 在類的方法或屬性中,this代表該方法或屬性的對象
  • 在類的構造器中,this代表該構造器即将傳回的隊形
  • 在擴充行數或帶接收者的函數字面值中,this表示(.)左邊的接收者
  • 如果this沒有限定符,那麼優先代表包含該this的最内層的接收者,并且會自動向外搜尋,如果要讓this明确引用特定的接收者,則可使用标簽限定符
class DiscernVariable{//隐式标簽@DiscernVariable
    private val prop="外部類的屬性"
    inner class InClass{//隐式标簽@InClass
        private val prop="内部類的屬性"
        fun info(){
            val prop="局部變量"
            //通過外部類類名.this.varname來通路外部類屬性
            println("外部類的屬性值:${[email protected]}")
            //通過this.varname來通路内部類屬性
            println("内部類的屬性值:${this.prop}")
            //直接通路局部變量
            println("局部變量的值${prop}")

        }
    }
    fun test(){
        val ic=InClass()
        ic.info()
    }

}
fun main(args:Array<String>){
    DiscernVariable().test()
}
           

列印:

外部類的屬性值:外部類的屬性

内部類的屬性值:内部類的屬性

局部變量的值局部變量

====================

内部類成員不能夠外部類直接使用,如果需要通路外部類成員,則必須顯式建立内部類對象來調用通路其成員

class Outer{
    private val outProp=9
    inner class Inner{
        val inprop=5
        fun accessOuterProp(){
            //内部類可以直接通路外部類的private屬性
            println("外部類的outProp值:${outProp}")
        }
    }
    fun accessInnerProp(){
        //如果需要通路内部類的屬性,必須顯式地建立内部類的對象
        println("内部類的inProp值:${Inner().inprop}")
    }
}

fun main(args:Array<String>){
    val out=Outer()
    out.accessInnerProp()
}
           

列印

内部類的inProp值:5

22、嵌套類

由于kotlin取消static修飾符,是以kotlin類的成員除嵌套類之外,全部都是非靜态成員,是以嵌套類不可通路外部類的其他任何成員(隻能通路其他嵌套類)

class NestedClassTest{
    var prop=5
    fun test(){
        println("外部類的test()方法")
    }
    //沒有inner修飾符,是嵌套類(相當于java的靜态内部類)
    class  NestedClass {
        fun accessOuterMember(){
            //通路另一個嵌套類是允許的
            val a = A()
            //下面兩行會出錯
            //println(prop)不可以通路,外部類的成員
            //test()
        }
    }

    class A{}
}

fun main(args:Array<String>){
    val out=Outer()
    out.accessInnerProp()
}
           

嵌套類唯一可以通路的是外部類的其他嵌套類

嵌套類相當于外部類的靜态成員,是以外部類的所有方法,屬性,初始化快都可以使用嵌套類來定義變量,建立對象等

外部類依然不能直接通路嵌套類的成員,但可以使用嵌套類的對象作為調用者來通路嵌套類的成員

class NestedClassTest{

    class  NestedClass {
        var prop=9
    }
        fun accessNetedProp(){
            println(NestedClass().prop)
        }
   
}
           

23、在外部類以外使用内部類

外部類在内部使用嵌套類或者内部類是與平常使用普通類沒有太大的差別,一樣可以直接通過嵌套類或内部類的類名來定義變量,調用嵌套類或者内部類的構造器來建立執行個體

嵌套類智能通路外部的其他嵌套類,不能通路外部的其他任何成員。如果希望在外部意外的地方使用内部類或嵌套類,一定要注意通路權限的限制。

在外部類以外的地方定義内部類變量的文法格式

var |val varname:OuterClass.InnerClass
           

從文法格式上看,在外部類意外的地方使用内部類時,内部類完整的類名應該是OuterClass.InnerClass,如果外部類還有包名,應當加上包名字首

由于内部類的對象必須寄生在外部類的對象中,是以在建立内部類對象之前,必須先建立器外部類對象。在外部類意外的地方建立内部類執行個體的文法格式如下

OuterInstance.InnerConstructor()
           

從以上文法格式可以看出,在外部類以外的地方建立内部類執行個體時必須使用外部類執行個體來調用内部類的構造器

class Outer{
    //定義一個内部類,不使用通路控制符,預設是public
    inner class In(msg:String){
        init {
            println(msg)
        }
    }
}

fun main(args:Array<String>){
    var oi:Outer.In=Outer().In("測試資訊")
    //以上代碼等效于
    //使用OuterClass.InnerClass形式定義内部類變量
    //var oi:Outer.In
    //建立外部類執行個體
    //val ot=Outer()
    //通過外部類執行個體來調用内部類的構造器建立内部執行個體
    //oi=ot.In("測試資訊")

}
           

測試資訊

24、在外部類以外使用嵌套類

由于内部類是外部類本身的成員,是以建立嵌套内部類對象無需建立外部類對象
           

在外部類以外的地方建立嵌套類執行個體的文法格式如下

案例

class NestedOut{
    //定義一個嵌套類,不适用通路控制符,預設是public
    open class Nested{
        init {
            println("嵌套類的構造器")
        }
    }
}


fun main(args:Array<String>){
   
    val nested:NestedOut.Nested=NestedOut.Nested()

}
           

列印

嵌套類的構造器

25、局部嵌套類

  • 放在方法或函數中定義的嵌套類就叫做局部嵌套類。
  • 局部嵌套類盡在該方法或函數中有效,由于局部嵌套了诶不能在方法或函數以外的地方使用,是以局部嵌套類也不能使用通路控制符修飾
  • 如果需要用局部嵌套類定義變量,建立執行個體或派生子類,隻能在局部嵌套類所在的方法或函數中進行。
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=8
        println("NestedSub對象的a和b屬性是:${ns.a},${ns.b}")
    }
}
fun main(args:Array<String>){
    LocalNestedClass().info()

}
           

列印

NestedSub對象的a和b屬性是:5,8

26、匿名内部類

kotlin抛棄了java的匿名内部類功能,提供了更加強大的文法,對象表達式

27、對象表達式

對象表達式和匿名内部類差別

匿名内部類隻能知道你個一個父類型(父接口或父類)

對象表達式課指定0-n個父類型(父接口或父類)

對象表達式的文法格式如下

object[:0-個父類型]{
//對象表達式的類體部分
}
           

對象表達式還有如下規則

  • 對象表達式不能是抽象類,應為要建立執行個體
  • 對象表達式不能定義構造器,但對象表灑水可以定義初始化快,可以通過初始化快來完成構造器需要完成的事情
  • 對象表達式可以包含内部類,但不能包含嵌套類
interface Outputable {
    fun output(msg:String)
}

abstract class Product(var price:Double){
    abstract  val name:String
    abstract fun printInfo()
}


fun main(args:Array<String>){
    //指定一個父類型或接口的對象表達式
    var ob1=object :Outputable{
        override fun output(msg: String) {
            for(i in 1..6){
                println("<h${i}>${msg}</h${i}>")
            }
        }
    }

    ob1.output("瘋狂教育中心")
    println("------")

    //指定零個父類型的對象表達水
    var ob2=object{
        //初始化快
        init {
            println("初始化快")
        }
        //屬性
        var name="kotlin"
        //方法
        fun test(){
            println("test方法")
        }

        //隻能包含内部類,不能包含嵌套類
        inner class Foo
    }
    println(ob2.name)
    ob2.test()
    println("------")

    //指定兩個父類型的對象表達式
    //由于Product隻有一個帶參數的構造器,是以需要傳入構造器參數
    var ob3=object:Outputable,Product(28.8){
        override fun output(msg: String) {
            println("輸出資訊:+msg")
        }
        override val name:String
        get() = "雷射列印機"

        override fun printInfo() {
            println("高速雷射大衣你急,支援自動雙面列印")
        }
    }
    println(ob3.name)
    ob3.output("kotlin真不錯")
    ob3.printInfo()
    
}
           

列印:

<h1>瘋狂教育中心</h1>
<h2>瘋狂教育中心</h2>
<h3>瘋狂教育中心</h3>
<h4>瘋狂教育中心</h4>
<h5>瘋狂教育中心</h5>
<h6>瘋狂教育中心</h6>
------
初始化快
kotlin
test方法
------
雷射列印機
輸出資訊:+msg
高速雷射大衣你急,支援自動雙面列印
           

28、對象聲明和單例模式

文法格式為:

object ObjectName[:0-個父類型]{
//對象表達式的類體部分
}
           

對象聲明和對象表達式的差別

  • 對象表達式是一個表達式,不能包含内部類,而對象聲明不是表達式,不能指派
  • 對象聲明課把傲寒嵌套類,不能包含内部類,而對象表達式可包含内部類,不能包含嵌套類
  • 對象聲明不能定義在函數和方法内,但對象表達式可嵌套在其他對象聲明或非内部類中
interface Outputable {
    fun output(msg:String)
}

abstract class Product(var price:Double){
    abstract  val name:String
    abstract fun printInfo()
}

//指定一個父類型(接口)的對象表達式
object Myobject1:Outputable{
    override fun output(msg: String) {
        for(i in 1..6){
            println("<h${i}>${msg}</h${i}>")
        }
    }
}

//指定零個父類型(接口)的對象表達式
object Myobject2{
    //初始化快
    init {
        println("初始化快")
    }
    //屬性
    var name="kotlin"
    //方法
    fun test(){
        println("test方法")
    }

    //隻能包含内部類,不能包含嵌套類
    class Foo
}

//指定兩個父類型的對象表達式
//由于Product隻有一個帶參數的構造器,是以需要傳入構造器參數
object Myobject3:Outputable,Product(28.8){
    override fun output(msg: String) {
        println("輸出資訊:+msg")
    }
    override val name:String
        get() = "雷射列印機"

    override fun printInfo() {
        println("高速雷射列印機,支援自動雙面列印")
    }
}
fun main(args:Array<String>){
   
    Myobject1.output("瘋狂教育中心")
    println("------")
    
    println(Myobject2.name)
    Myobject2.test()
    println("------")

    
    println(Myobject3.name)
    Myobject3.output("kotlin真不錯")
    Myobject3.printInfo()

}
           

列印

<h1>瘋狂教育中心</h1>
<h2>瘋狂教育中心</h2>
<h3>瘋狂教育中心</h3>
<h4>瘋狂教育中心</h4>
<h5>瘋狂教育中心</h5>
<h6>瘋狂教育中心</h6>
------
初始化快
kotlin
test方法
------
雷射列印機
輸出資訊:+msg
高速雷射列印機,支援自動雙面列印
           

29、伴生對象和靜态成員

  • 在類中定義的對象聲明,可使用companion修飾,這樣該對象就變成了伴生對象
  • 每個類最多隻能定義一個伴生對象,伴生對象相當于外部類的對象,程式可以通過外部類直接調用伴生對象的成員。
interface Outputable {
    fun output(msg:String)
}
class Myclass{
    //使用compantion修飾的伴生對象
    companion object Myobject1:Outputable{
        val name="name屬性值"
        override fun output(msg: String) {
            for (i in 1..6){
                println("<h${i}>${msg}</h${i}>")

            }
        }
    }
}

fun main(args:Array<String>){
    //使用伴生對象所在的類調用伴生對象的方法
    Myclass.output("瘋狂教育")//使用類名直接調用
    println(Myclass.name)//使用類名直接調用
}
           

列印:

<h1>瘋狂教育</h1>
<h2>瘋狂教育</h2>
<h3>瘋狂教育</h3>
<h4>瘋狂教育</h4>
<h5>瘋狂教育</h5>
<h6>瘋狂教育</h6>
name屬性值
           

從以上代碼中可以看到,伴生對象的名稱不重要(myobject1根本沒用到),是以伴生對象可以省略名稱。省略名稱後,如果程式真的要通路伴生對象,則可以他通過companion名稱進行通路

interface Outputable {
    fun output(msg:String)
}
class Myclass{
    //使用compantion修飾的伴生對象
    companion object :Outputable{
        val name="name屬性值"
        override fun output(msg: String) {
            for (i in 1..6){
                println("<h${i}>${msg}</h${i}>")

            }
        }
    }
}

fun main(args:Array<String>){
    //使用伴生對象所在的類調用伴生對象的方法
    Myclass.output("瘋狂教育")//使用類名直接調用
    println(Myclass.Companion)//使用類名調用companion關鍵字
}
           

30、伴生對象的擴充

為伴生對象擴充的方法和屬性,就相當于為伴生對象所在的外部類擴充了靜态成員,可通過外部類的類名通路這些擴充成員。

interface Outputable {
    fun output(msg:String)
}
class Myclass{
    //使用compantion修飾的伴生對象
    companion object :Outputable{
        val name="name屬性值"
        override fun output(msg: String) {
            for (i in 1..6){
                println("<h${i}>${msg}</h${i}>")

            }
        }
    }
}

//為伴生對象擴充的方法
fun Myclass.Companion.test(){
    println("為伴生對象擴充的方法")
}

//為伴生對象擴充的屬性
val Myclass.Companion.foo
get() = "為伴生對象擴充的屬性"


fun main(args:Array<String>){
    //使用伴生對象所在的類調用伴生對象的方法
    Myclass.output("瘋狂教育")//使用類名直接調用
    println(Myclass.name)//使用類名調用companion關鍵字
    //通過伴生對象所在對的類調用為伴生對象擴充的成員
    Myclass.test()
    println(Myclass.foo)
}
           

列印

<h1>瘋狂教育</h1>
<h2>瘋狂教育</h2>
<h3>瘋狂教育</h3>
<h4>瘋狂教育</h4>
<h5>瘋狂教育</h5>
<h6>瘋狂教育</h6>
name屬性值
為伴生對象擴充的方法
為伴生對象擴充的屬性
           

31、枚舉類入門

枚舉類是一種特殊的類,可以有自己的屬性、方法,可以實作一個或多個接口,也可以定義自己的構造器

枚舉類與普通類的差別

  • 枚舉類可以實作一個或多個接口,繼承自kotlin.Enum類
  • 使用enum定義的非抽象的枚舉類不能使用open修飾,不能派生子類
  • 枚舉類的構造器隻能使用private通路控制符,如果省略了構造器的通路控制符,則預設private修飾
  • 枚舉類的所有執行個體必須在枚舉類的第一行顯式列出

枚舉類預設提供如下兩個方法

- EnumClass.valueOf(value:String):EnumClass
 - EnumClass.values:Array<EnumClass>

           

案例

enum class Season{
//在第一行列出4個枚舉執行個體
    SPRING,SUMMER,FALL,WINTER
}

fun main(args:Array<String>){
   //枚舉預設有一個values()方法,傳回該枚舉類所有執行個體(因為不是繼承自Object,而是繼承制ENUM類)
    for (s in Season.values()){
        println(s)
    }
    val seasonName="SUMMER"
    val s:Season=Season.valueOf(seasonName)
    println("-----")
    println(s)
    println("-----")
    //直接通路枚舉值
    println(Season.WINTER)

}
           

列印

SPRING
SUMMER
FALL
WINTER
-----
SUMMER
-----
WINTER
           

由于枚舉類繼承自kotli.ENUM類,父類提供了如下屬性和方法

  • name屬性,傳回此枚舉執行個體的名稱,也就是枚舉值之一
  • ordinal屬性,傳回枚舉值在枚舉類中的索引值,從零0
  • intcompareTo(E o)方法:用于與指定對的枚舉對象比較順序
  • String toString():傳回枚舉常量的名稱,與name屬性相似,但toString方法用得更多

32、枚舉類的屬性、方法、構造器

  • 枚舉類式設計為不可變類,是以他的屬性值不允許改變,這樣會更安全,是以盡職開發者對屬性指派,并推薦是用val為枚舉聲明隻讀屬性
  • 由于是隻讀屬性,枚舉必須在構造器中為這些屬性指定初始值(或在初始化快中指定初始值,但少用)是以應當為枚舉類顯示定義帶參數的構造器
  • 一旦為枚舉類顯式地定義了帶參數的構造器你,在列出枚舉值時就必須對應地傳入參數
//使用主構造器聲明cnname隻讀屬性
enum class Gender(var cnName:String){
    MALE("男"),FAMALE("女");//因為後面有額外的枚舉成員,這裡用了分号結束,等同有MALE=new Gender("男")
    //定義方法
    fun info(){
        when(this){
            MALE-> println("天行健,君子以自強不息")
            FAMALE-> println("唯小人與女子難養也")
        }
    }

}

fun main(args:Array<String>){
  //通過Gender的valueOf()方法更久枚舉名擷取枚舉值
    val g=Gender.valueOf("FAMALE")
    //通路枚舉值的cnName屬性
    println("${g}代表${g.name}")
    //調用info方法
    g.info()//g不是枚舉值嗎,怎麼又成了枚舉執行個體了,因為枚舉類的每一枚舉值就是一個枚舉執行個體

}
           

列印:

FAMALE代表FAMALE

唯小人與女子難養也

33、實作接口的枚舉類

枚舉類實作接口也需要實作該接口所包含的方法

interface GenderDesc {
    fun info()
}
//使用主構造器聲明cnname隻讀屬性
enum class Gender(var cnName:String):GenderDesc{
    MALE("男"),FAMALE("女");//因為後面有額外的枚舉成員,這裡用了分号結束,等同有MALE=new Gender("男")
    //定義方法
    override  fun info(){
        when(this){
            MALE-> println("天行健,君子以自強不息")
            FAMALE-> println("唯小人與女子難養也")
        }
    }

}

fun main(args:Array<String>){
  //通過Gender的valueOf()方法更久枚舉名擷取枚舉值
    val g=Gender.valueOf("FAMALE")
    //通路枚舉值的cnName屬性
    println("${g}代表${g.name}")
    //調用info方法
    g.info()//g不是枚舉值嗎,怎麼又成了枚舉執行個體了,因為枚舉類的每一枚舉值就是一個枚舉執行個體

}
           

列印:

FAMALE代表FAMALE

唯小人與女子難養也

34、包含抽象方法的抽象枚舉類

  • 枚舉類中包含抽象方法時依然不能使用abstract修飾,系統會自行添加上去
  • 在定義每個枚舉值時必須為抽象成員提供實作

案例:每個枚舉操作定義具體的計算方法,來實作抽象計算方法

enum class Operation{
    PLUS{
        override fun eval(x: Double, y: Double): Double {
            return x+y
        }
    },
    MINUS{
        override fun eval(x: Double,y: Double)=x-y
    },
    TIMS{
        override fun eval(x: Double, y: Double): Double {
            return x*y
        }
    },
    DIVIDE{
        override fun eval(x: Double, y: Double): Double {
            return x/y
        }
    };


    //為枚舉類定義一個抽象方法,這個抽象方法由不同的枚舉值提提供不同的實作
    abstract fun eval(x:Double,y: Double):Double
}

fun main(args:Array<String>){
    println(Operation.PLUS.eval(2.0,4.0))
    println(Operation.MINUS.eval(5.0,3.0))
    println(Operation.TIMS.eval(3.0,6.0))
    println(Operation.DIVIDE.eval(5.0,4.0))

}
           

列印:

6.0

2.0

18.0

1.25

35、類委托

kotlin的委托分為類委拖和屬性委托

類委托的本質就是将本類需要實作的部分方法委托給了其他對象----相當于借用其他對象的方法作為自己的實作。

使用by關鍵字進行委托

interface Outputable {
    fun output(msg:String)
    var type:String
}
//定義一個DefaultOuput類實作outputable接口
class DefaultOutput:Outputable{
        override fun output(msg: String) {
            for (i in 1..6){
                println("<h${i}>${msg}</h${i}>")

            }
        }
    override var type:String="輸出裝置"

}

//定義Printer類,指定構造參數b作為委托對象
class Printer(b:DefaultOutput):Outputable by b
//定義projecto類,指定建立的對象作為委托對象
class Projector():Outputable by DefaultOutput(){
    override fun output(msg: String) {
        javax.swing.JOptionPane.showMessageDialog(null,msg)
    }
}


fun main(args:Array<String>){
    val output=DefaultOutput()
    //printer對象的委托對象是output
    var printer=Printer(output)
    //其實就是調用委托對象的output()方法
    printer.output("瘋狂教育機構")
    //其實就是調用委托對象的type屬性方法
    println(printer.type)
    //projector對象的委托對象也是output
    var projector =Projector()
    //projector本身重寫了outpu方法,是以此處是調用本類重寫的方法
    projector.output("瘋狂軟體中心")
    //其實就是調用委托對象的type屬性方法
    println(projector.type)

}
           

列印:

<h1>瘋狂教育機構</h1>
<h2>瘋狂教育機構</h2>
<h3>瘋狂教育機構</h3>
<h4>瘋狂教育機構</h4>
<h5>瘋狂教育機構</h5>
<h6>瘋狂教育機構</h6>
輸出裝置
輸出裝置
           

36、屬性委托(沒阿太看懂)

屬性委托可以将多個類的類似屬性同意交給委托對象集中實作,避免每個類都要單獨實作這些屬性

對于制定了委托對象的屬性而言,由于她的實作邏輯已經交給了委托對象處理,是以開發者不能為委托屬性提供getter和setter方法。

一旦将某個對象指定為屬性的未委托對象,該對象就會全面接管該屬性讀取getter和寫入setter操作,是以,屬性的未談妥對象無需實作任何接口。但一定要提供一個getValue()方法和setValue()方法

使用operator修飾符

import kotlin.reflect.KProperty

class PropertyDelegation{
    //該屬性的委托對象是MyDelegation
    var name:String by MyDelegation()

}
class MyDelegation{
    private var _backValue="預設值"
    operator fun getValue(thisRef:PropertyDelegation,property:KProperty<*>):String{
        println("${thisRef}的${property.name}屬性執行getter方法")
        return _backValue
    }
    operator fun setValue(thisRef: PropertyDelegation,property: KProperty<*>,newValue:String){
        println("${thisRef}的${property.name}屬性執行settter方法,傳入參數值為:${newValue}")
        _backValue=newValue
    }


}

fun main(args:Array<String>){
    val pd=PropertyDelegation()
    //讀取屬性,實際上是調用屬性的委托對象的getter方法
    println(pd.name)
    //寫入屬性,實際上是調用屬性的委托對象的setter方法
    pd.name="瘋狂教育中心"
    println(pd.name)
}
           

列印:

[email protected]的name屬性執行getter方法

預設值

[email protected]的name屬性執行settter方法,傳入參數值為:瘋狂教育中心

[email protected]的name屬性執行getter方法

瘋狂教育中心

37、延遲屬性

  • kotlin提供了一個lazy()函數,該函數接受一個lambda表達式作為參數,并傳回一個lazy< T >對象
  • lazy < T > 對象包含了一個符合隻讀屬性委托要求的getValue()方法,是以該lazy對象可作為隻讀屬性的委托對象
val lazyProp:String by lazy {
    println("第一次通路時執行代碼塊")
    "瘋狂軟體中心"
}

fun main(args:Array<String>){
    //兩次通路lazyProp屬性的值
    println(lazyProp)//第一次正常加載
    println(lazyProp)//第二次加載不在計算lambda表達式,而是直接傳回第一次的值
}
           

列印:

第一次通路時執行代碼塊

瘋狂軟體中心

瘋狂軟體中心

lazy < T >的getValue()方法的處理邏輯是,第一次調用該方法适合,程式會計算lambda表達式,并得到其傳回值,以後程式再次調用該方法時,不再計算lambda表達式,而是直接使用第一次計算得到的傳回值

38、屬性監聽

兩個監聽方法

  • fun < T >oberservable
  • fun < T >vetoable

    import kotlin.properties.Delegates

var observableProperty:String by Delegates.observable("預設值"){
    //lambda表達式的第一個參數代表被監聽的屬性
    //第二個參數代表修改之前的值
    //第三個參數代表被設定的新值
    property, oldValue, newValue ->
    println("${property}的${oldValue}被改為${newValue}")
}

fun main(args:Array<String>){
    //通路observableProp屬性,不會觸發監聽器的lambda表達式
    println(observableProperty)
    //設定屬性值,觸發監聽器的lambda表達式
    observableProperty="瘋狂軟體"
    println(observableProperty)

}
           

列印:

預設值

var observableProperty: kotlin.String的預設值被改為瘋狂軟體

瘋狂軟體

import kotlin.properties.Delegates

var vetoableProp:String by Delegates.vetoable("預設值"){
    //lambda表達式的第一個參數代表被監聽的屬性
    //第二個參數代表修改之前的值
    //第三個參數代表被設定的新值
    property, oldValue, newValue ->
    println("${property}的${oldValue}被改為${newValue}")
    newValue>oldValue
}

fun main(args:Array<String>){
    //通路vetoable屬性,不會觸發監聽器的lambda表達式
    println(vetoableProp)
    //新值小于舊值,監聽的lambda表達式false,新值設定失敗
    vetoableProp= 15.toString()
    println(vetoableProp)
    //新值大于舊值,監聽的lambda表達式true,新值設定成功
    vetoableProp= 25.toString()
    println(vetoableProp)

}
           

列印:

預設值

var vetoableProp: kotlin.String的預設值被改為15

預設值

var vetoableProp: kotlin.String的預設值被改為25

預設值

39、使用Map存儲屬性值

kotlin的map提供了如下方法:

operator fun....Map...getValue
           

MutableMap提供兩個方法

operator fun....Map...getValue
operator fun....Map...setValue
           
  • 意味着mutableMap對象可作為讀寫對象的委托
  • 程式将類制度屬性委托給Map對象管理,在這種情況下,對象本身并不負責存儲對象狀态,而是将對象狀态儲存在Map集合中。
  • 這麼做的好處是,當程式需要和外部接口通信時,程式并不需要将該對象直接暴露出來,隻要将該對象屬性所委托的Map暴露出來即可,而Map則完整地儲存了整個對象狀态。
  • 下例将所有的隻讀屬性都委托給一個Map對象,Item對象的每個屬性名就相當于Map的key,屬性值就相當于key對應的value
class Item(val map:Map<String,Any?>){
    val barCode:String by map
    val name:String by map
    val price:Double by map
}
fun main(args:Array<String>){
    val item=Item(mapOf(
            "barCode" to "12354",
            "name" to "瘋狂kotlin講義",
            "price" to 68.9
    ))

    println(item.barCode)
    println(item.name)
    println(item.price)
    println("*********")
    //将對象持有的map暴露出來,其他程式可以通過标準map讀取資料

    val map=item.map
    println(map["barCode"])
    println(map["name"])
    println(map["price"])
}
           

列印

12354

瘋狂kotlin講義

68.9

12354

瘋狂kotlin講義

68.9

上例改為MutableMap也是成立的,并且另外加了寫的屬性

class MutableItem(val map:MutableMap<String,Any?>){
    val barCode:String by map
    val name:String by map
    val price:Double by map
}
fun main(args:Array<String>){
    val item=MutableItem(mutableMapOf(
            "barCode" to "12354",
            "name" to "瘋狂kotlin講義",
            "price" to 68.9
    ))

    println(item.barCode)
    println(item.name)
    println(item.price)
    println("*********")
    //将對象持有的map暴露出來,其他程式可以通過标準map讀取資料

    val map=item.map
    println(map["barCode"])
    println(map["name"])
    println(map["price"])
    map["price"]=999.9
    println(item.price)
}
           

列印

12354

瘋狂kotlin講義

68.9

12354

瘋狂kotlin講義

68.9

999.9

40、局部屬性委托

指定了委托對象的局部變量誠摯為“局部委托屬性”-其實還是局部變量。隻是對該變量的讀取、指派操作都交給了委托對象去實作

局部變量的委托對象,對于隻讀屬性要實作operator修飾的getValue方法,該方法第一個參數是Nothing?類型。代表永不存在的對象

同樣的以上讀寫屬性也類似

import kotlin.reflect.KProperty

class Mydelegation{
    private var _backValue="預設值"
    operator fun getValue(thisRef:Nothing?,property:KProperty<*>):String{
        println("${thisRef}的${property.name}屬性執行getter方法")
        return _backValue
    }
    operator fun setValue(thisRef:Nothing?,property:KProperty<*>,newValue:String) {
        println("${thisRef}的${property.name}屬性執行setter方法,傳入參數值為${newValue}")
        _backValue=newValue
    }

}
fun main(args:Array<String>){
    var name:String by Mydelegation()
    //通路局部變量,實際是調用mydelegete對象的getValue()方法
    println(name)
    //對局部變量指派,實際是調用mydelegete對象的setValue()方法
    name="新的值"
    println(name)

}
           

列印

null的name屬性執行getter方法

預設值

null的name屬性執行setter方法,傳入參數值為新的值

null的name屬性執行getter方法

新的值

可以利用lazy()函數對局部變量延遲初始化

41、委托工廠(真心沒懂)

委托工廠對象也可以作為委托對象,委托工廠需要提供如下方法

operator fun provideDelegate:該方法的參數與委托的getValue方法的兩個參數的意義相同

import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty

class MyTarget{
    //該屬性的委托對象是provideDelegate()方法傳回的mydelegation對象
    var name:String by PropertyChecker()
}

class PropertyChecker () {
    operator fun providerDelegate(thisRef: MyTarget,property: KProperty<*>):ReadWriteProperty<MyTarget,String>{
        //插入自定義代碼,可執行任意業務操作
        checkProperty(thisRef,property.name)
        //傳回實際委托對象
        return Mydelegation()
    }

    private fun checkProperty(thisRef: MyTarget, name: String) {
        println("檢查屬性")
    }
}

class Mydelegation:ReadWriteProperty<MyTarget,String>{
    private var _backValue="預設值"
    override fun getValue(thisRef:Nothing?,property:KProperty<*>):String{
        println("${thisRef}的${property.name}屬性執行getter方法")
        return _backValue
    }
    override fun setValue(thisRef:Nothing?,property:KProperty<*>,newValue:String) {
        println("${thisRef}的${property.name}屬性執行setter方法,傳入參數值為${newValue}")
        _backValue=newValue
    }
}


fun main(args:Array<String>){
    //建立對象(初始化屬性),調用委托工廠的providerDelegate()方法
    val pd=MyTarget()
    //讀取屬性,實際上是調用屬性委托對象的getter方法
    println(pd.name)
    //寫入屬性,實際上是調用屬性委托對象的setter方法
    pd.name="瘋狂教育"
    println(pd.name)
}
           

繼續閱讀