天天看點

瘋狂Kotlin講義學習筆記08章:面向對象(中)--擴充,匿名函數,宏替換,final,抽象類,密封類,接口

1、擴充方法

實際就是繼承=>派生的意思

被繼承需要使用open關鍵字修飾

使用類名.方法名的方式為父類擴充方法,這樣父類和子類都可以共享該方法

open class Base{
    fun test(){
        println("這是基類的方法")
    }
}
class Sub:Base(){
    fun ext(){
        println("這是子類的方法")
    }
}
//為基類擴充一個方法
fun Base.info(){
    println("擴充出來的方法")
}

fun main(args:Array<String>){
    var base=Base()
    base.test()
    var son=Sub()
    son.ext()

    base.info()
    son.info()

}
           

列印

這是基類的方法

這是子類的方法

擴充出來的方法

擴充出來的方法

2、擴充的實作機制

擴充實際就是調用了定義的擴充方法

成員方法執行動态解析(由運作時類型決定);擴充方法執行靜态解析(由編譯類型決定)

3、為可空類型擴充方法

允許對可空類型擴充方法,由于可空類型允許接收null值,這樣使得null值也可調用該擴充方法

fun Any?.equals(other:Any?):Boolean{
    if(this==null){
        return if (other==null) true else false
    }
    return other.equals(other)
}

fun main(args:Array<String>){
    var a=null
    println(a.equals(null))
    println(a.equals("kotlin"))

}
           

列印:

true

false

4、擴充屬性

擴充屬性實際上市通過添加getter和setter方法實作的,擴充的屬性隻能是計算屬性

  • 擴充屬性不能有初始值
  • 不能用field關鍵字顯示通路幕後字段
  • 擴充隻讀屬性必須提供getter方法,擴充讀寫屬性必須提供getter和setter方法
class User(var firstname:String, var lastname:String){}
//為User擴充屬性
var User.fullname:String
    get()="${firstname}.${lastname}"
    set(value){
        var tokens=value.split(".")
        firstname=tokens[0]
        lastname=tokens[1]
    }

fun main(args:Array<String>){
    var user=User("悟空","孫")
    println(user.fullname)

    user.fullname="八戒.豬"
    println(user.firstname)
    println(user.lastname)

}
           

列印:

悟空.孫

八戒

5、以成員方式定義擴充

以類成員的方式定義擴充–就向為類定義方法、屬性哪樣定義擴充

class A{
    fun bar()= println("A的bar方法")
}
class B{
    fun baz()= println("B的baz方法")
    //以成員方法為A擴充foo()方法
    fun A.foo(){
        //在該擴充方法裡面,既可以調用類A的成員,也可調用類B的成員
        bar()
        baz()
    }

    fun test(target:A) {
        //調用A對象的成員方法
        target.bar()
        //調用A對象的擴充方法
        target.foo()
    }
}
fun main(args:Array<String>){
    var b=B()
    b.test(A())

}
           

列印:

A的bar方法

A的bar方法

B的baz方法

程式在擴充方法中調用兩個類都包含方法的時候,系統總是優先調用被擴充類的方法,為了讓系統調用擴充定義所在類的方法,必須使用帶标簽的this進行限定:[email protected]

class Tiger{
    fun foo(){
        println("Tiger類的foo()方法")
    }
}

class Bear{
    fun foo(){
        println("Bear類的foo()方法")
    }
    //以成員方式為Tiger類擴充test方法
    fun Tiger.test(){
        //同名函數調用時,優先調用被擴充類的test()方法
        foo()
        //使用帶标簽的this指定Bear的foo()方法
        [email protected]()
    }
    fun info(tiger: Tiger){
        tiger.test()
    }
}
fun main(args:Array<String>){
    var b=Bear()
    b.info(Tiger())

}
           

列印:

Tiger類的foo()方法

Bear類的foo()方法

6、帶接收者的匿名函數

kotlin制程為類擴充匿名函數,該擴充函數所屬的類也是該函數的接收者。這種匿名函數稱之為“帶接收者的匿名函數”

val factorial=fun Int.():Int{
    //該匿名函數的接收者是Int對象
    //是以在該匿名函數中,this代表調用該匿名函數的Int對象
    if (this<0){
        return -1
    }else if (this==1){
        return 1
    }else{
        var result=1
        for (i in 1..this){
            result*=i
        }
        return  result
    }
}

fun main(args:Array<String>){
    println(6.factorial())

}
           

列印

720

與普通函數想類似的是,帶接收者的匿名函數也有自身的類型,即帶接收者的函數類型。如上例factorial變量的類型為:Int.()->Int

class HTML{
    fun body(){
        println("<body></body>")
    }
    fun head(){
        println("<head></head")
    }


}
//定義一個類型為HTML.()->Int的形參(帶接收者的匿名函數)
//這樣在函數中HTML對象就增加了一個init方法
fun html(init:HTML.()->Unit){
    println("<html>")
    val html=HTML()//建立接收者對象
    html.init()
    println("</html>")
}

fun main(args:Array<String>){
    //調用html函數,需要傳入HTML.()->Unit類型的參數
    //此時系統可推斷出接收者的類型,故可以用lambda表達式代替匿名函數

    html{
        //lambda表達式中的this就是該方法的調用者
        head()
        body()
    }
}
           

列印:

<html>
<head></head
<body></body>
</html>
           

7、何時使用擴充

兩個作用

  • 擴充可動态地為已有的類添加方法或屬性
  • 擴充能以更好的形式組織一些工具方法

8、可執行“宏替換”的常量

kotlin的final修飾符不能修飾局部變量,是以open自然也不能修飾局部變量

kotlin不允許使用final定義“宏變量”

“宏替換”的常量除使用const修飾之外,還必須滿足如下條件

  • 位于頂層或者是對象表達式的成員
  • 初始值為基本類型值或者字元串字面值
  • 沒有自定義的getter方法
const val MAX_AGE=100

fun main(args:Array<String>){
    println(MAX_AGE)
}
           

列印:

100

9、final屬性

final表示該屬性不能被重寫,為所有的屬性自動添加final修飾

10、final方法

final表示該方法不能被重寫,為所有的方法自動添加final修飾

11、final類

用final修飾的類不可以有子類,為所有類自動添加final修飾

12、不可變類(immutable)

不可變類的意思是建立該類的執行個體後,該執行個體的屬性值是不可改變的

如果需要建立自定義的不可變類,可遵循如下規則

  • 提供帶參的構造器,用于根據傳入的參數來初始化類中的屬性
  • 定義使用final修飾的隻讀屬性,避免程式通過setter方法改變屬性值

    如果有必要,則重寫Any類的hashCode()和equals()方法,equals方法将關鍵屬性作為兩個對象是否相等的标準。除此之外,還應保證兩個用equals()方法判斷為相等的對象的hashcode()也相等

當建立不可變類時,如果她包含的成員屬性的類型是可變的,那麼其對象的屬性值依然是可變的。

class Name(var firstName:String="",var lastName:String=""){//兩個形參(屬性)是可變的

}

class Person(val name:Name){//兩個形參是不可變的

}

fun main(args:Array<String>){
    val n=Name("悟空","孫")
    var p=Person(n)

    //Person對象的name的fistname為“悟空”
    println(p.name.firstName)//連續傳遞對象及其屬性過來
    //改變Person對象的name的firstname值
    n.firstName="八戒"
    //Person對象的name的firstname被改為“八戒”,因為Name中的兩個屬性實際上是可變的
    println(p.name.firstName)//連續傳遞對象及其屬性過來

}
           

列印:

悟空

八戒

為了保持Person對象的不可變性,必須好保護好Person對象的應用類型的屬性:name,讓程式無法通路到Person對象的name屬性的幕後變量,也就無法利用name屬性的可變性來改變Person對象了

13、抽象成員和抽象類

使用abstract修飾的成員,無須使用open修飾。

當使用abstract修飾方法、屬性時,說明該類需要被繼承,方式屬性需要被子類重寫。

包含抽象成員的類隻能定義成抽象類,抽象類中可以沒有抽象成員

抽象方法和抽象類的規則

  • 類和抽象成員必須使用abstract來修飾
  • 抽象類不能被執行個體化
  • 抽象類可以包含屬性、方法(普通方法和抽象方法都可以)、構造器、初始化塊、嵌套類(接口、枚舉)成員。抽象類的構造器不能用于建立執行個體,隻能被其子類調用
  • 含有抽象成員的類隻能被定義成抽象類。
abstract class Shape{
    init {
        println("執行Shape抽象類的方法")
    }

    var color=""
    //定義一個周長的抽象方法
    abstract  fun calPerimete():Double
    //定義一個代表形狀的抽象的隻讀屬性,抽象屬性不需要初始值
    abstract val type:String
    //定義shape的構造器,該構造器并不是用于建立shape對象的,而是用于被子類調用
    constructor()
    constructor(color:String){
        println("執行Shape的構造器")
        this.color=color
    }
}

//定義三角形的三遍
class Triangle(color: String,var a:Double,var b:Double,var c:Double):Shape(color){
    fun setSides(a:Double,b:Double,c:Double){
        if(a>=b+c||b>a+c ||c>=a+b){
            println("三角形兩邊之和必須大于第三邊")
            return
        }
        this.a=a
        this.b=b
        this.c=c
    }
    //重寫shape類計算周長的方法
    override fun calPerimete(): Double {
        return a+b+c
    }
    //重寫shape類代表形狀的抽象屬性
    override val  type:String="三角形"
}

//定義一個圓
class Circle(color: String,var radius:Double):Shape(color){
    //重寫shape類計算周長的方法
    override fun calPerimete(): Double {
        return 2*Math.PI*radius
    }
    //重寫shape類代表形狀的抽象屬性
    override val  type:String="圓形"
}
fun main(args:Array<String>){
    var s1:Shape=Triangle("黑色",3.0,4.0,5.0)
    var s2:Shape=Circle("黃色",3.0)

    println(s1.type)
    println(s1.calPerimete())

    println(s2.type)
    println(s2.calPerimete())

}
           

列印:

執行Shape抽象類的方法

執行Shape的構造器

執行Shape抽象類的方法

執行Shape的構造器

三角形

12.0

圓形

18.84955592153876

14、抽象類的作用

從多個具有相同特征的類中抽象出一個抽象類,以這個抽象類作為子類的模闆,進而避免了子類設計的随意性。

抽象類是一種模闆模式的的設計,抽象類作為多個子類的通用模闆,子類在抽象類的基礎上進行擴充、改造,但子類總體上會大緻保留抽象類的行為方式

//定義帶轉速屬性的主構造器
abstract class SpeedMeter(var turnRate:Double){
    //把傳回車輪半徑的方法定義成抽象方法
    abstract  fun calGrith():Double
    //定義計算速度的通用算法
    fun getSpeed():Double{
        //速度等于車輪周長*轉速
        return calGrith()*turnRate
    }
}

//這裡抽象方法中無法定義倫則的周長(實際是輪子的半徑)
public class CarSpeedMeter(var radius:Double):SpeedMeter(0.0){
    override fun calGrith(): Double {
        return radius*2*Math.PI
    }
}


fun main(args:Array<String>){
    val csm=CarSpeedMeter(0.28)
    csm.turnRate=15.0
    println(csm.getSpeed())

}
           

列印:

26.389378290154266

SpeedMeter類提供了車速表的通用算法,但是具體到實作的細節,則推遲到其子類CarSpeedMeter類中實作,這是一種典型的模闆模式

模闆模式的一些簡單規則

  • 抽象父類可以隻定義需要使用的某些方式,把不能實作的部分抽象成抽象方法,留給子類去實作
  • 父類中可能包含需要調用其他系列方法的方法,這些被調用方法既可以由父類實作,也可以由其子類實作。父類中提供的方法隻是定義了一個通用算法,其實作也許并不完全由自身來完成,而必須依賴其子類的輔助

15、密封類

密封類是 一種特殊的抽象類,專門用于派生子類。

密封類使用sealed關鍵字

密封類與普通抽象類的差別在于

  • 密封類的子類是固定的,密封類的子類必須與密封類本身在同一個檔案中,在其他檔案中則不能為密封類派生子類,這樣就限制了在其他檔案中派生子類
  • 密封類的所有構造方法都是private的,自動添加
  • 密封類的直接子類必須在同一個檔案中,但是間接子類不需要在同一個檔案中
  • 使用密封類的好處是密封類的子類數量是有限的(在同一個檔案中不可能擁有太多直接子類)
//定義一個密封類,其實就是抽象類
sealed class Apple{
    abstract fun taste()
}

open class RedFuJi:Apple(){
    override fun taste() {
        println("紅富士蘋果香甜可口")
    }
}

data class Gala(var weight:Double):Apple(){
    override fun taste() {
        println("嘎啦果更清脆,重量為:${weight}")
    }
}


fun main(args:Array<String>){
    //使用Apple聲明變量,用子類執行個體指派
    var ap1:Apple=RedFuJi()
    var ap2:Apple=Gala(2.3)
    ap1.taste()
    ap2.taste()

}
           

列印

紅富士蘋果香甜可口

嘎啦果更清脆,重量為:2.3

16、接口的定義

與java的接口非常相識

基本文法如下

【修飾符】interface 接口名:父接口1,父接口2...{
零到多個屬性定義
零到多個方法定義
零到多個嵌套類,嵌套接口,嵌套枚舉定義
}
           
  • 接口隻能繼承接口不能繼承類
  • 預設采用public ,可以采用public、internal、private修飾
  • 接口定義的是一種規範,是以接口中不能包含構造器和初始化塊定義,
  • 接口中定義的方法既可以是抽象方法,也可以是非抽象方法,如果一個方法沒有方法體,或一直隻讀屬性沒有定義getter方法,或者一個讀寫屬性沒有定義getter方法和setter方法,則該屬性或方法為抽象的
  • 需要被實作類重寫的方法,用public修飾,預設也是public
  • 不需要被實作類重寫的方法可以用public也可以用private修飾
interface Outputable{
    //隻讀屬性定義了getter方法,非抽象屬性
    val name:String
    get()="輸出裝置"
    //隻讀屬性沒有定義getter方法,抽象屬性
    val brand:String
    //讀寫屬性沒有定義getter、setter方法,抽象屬性
    var category:String
    //接口定義的抽象方法
    fun out()
    fun getData(msg:String)
    //在接口中定義的非抽象方法,可使用private修飾
    fun print(vararg msgs:String){
        for (msg in msgs){
            println(msg)
        }
    }
    //在接口中定義的非抽象方法,可使用private修飾
    fun test(){
        println("接口中test()方法")
    }
    
}
           

17、接口的繼承

接口支援多繼承:一個接口可以有多個直接父接口,并獲得父接口中定義的所有方法、屬性

多繼承接口排在:後面,用逗号,隔開

interface InterfaceA{
    val propA:Int
        get()=5
        fun testA()
}

interface InterfaceB{
    val propB:Int
    get() = 6
    fun testB()
}

interface interfaceC:InterfaceA,InterfaceB{
    val propC:Int
    get() = 7
    fun testC()
}
           

18、使用接口

接口不能用于建立執行個體,但可以用于聲明變量,當使用接口來聲明變量時,這個引用類型的變量必須引用到其實作類的對象。

接口的主要用途

  • 就是被實作類實作
  • 定義變量,也可以用于強制類型轉換
  • 被其他類實作

一個類可以實作多個接口,直接将被實作的多個接口、父類放在英文冒号之後,且父類、接口之間沒有順序要求,隻需要用英文逗号隔開即可

interface Outputable{
    //隻讀屬性定義了getter方法,非抽象屬性
    val name:String
        get()="輸出裝置"
    //隻讀屬性沒有定義getter方法,抽象屬性
    val brand:String
    //讀寫屬性沒有定義getter、setter方法,抽象屬性
    var category:String
    //接口定義的抽象方法
    fun out()
    fun getData(msg:String)
    //在接口中定義的非抽象方法,可使用private修飾
    fun print(vararg msgs:String){
        for (msg in msgs){
            println(msg)
        }
    }
    //在接口中定義的非抽象方法,可使用private修飾
    fun test(){
        println("接口中test()方法")
    }

}
interface Product{
    fun getProductTime():Int

}

const val MAX_CACHE_LINE=10
//讓printer類實作Outputable和Product接口
class Printer:Outputable,Product{
    private val printData=Array<String>(MAX_CACHE_LINE,{""})
    //用以記錄目前需列印的作業數
    private var dataNum=0
    override val brand:String="HP"
    //重寫接口的抽象隻讀屬性
    override var category:String="輸出外設"
    override fun out() {
        //隻要還有作業就一直列印
        while (dataNum>0){
            println("列印機列印:"+printData[0])
            //把作業隊列整體前移一位,并将剩下的作業數減1
            System.arraycopy(printData,1,printData,0,--dataNum)
        }
    }

    override fun getData(msg: String) {
        if (dataNum>=MAX_CACHE_LINE){
            println("輸出隊列已滿,添加失敗")
        }else{
            //把列印資料添加到隊列裡,已儲存資料的數量加1
            printData[dataNum++]=msg
        }
    }

    override fun getProductTime(): Int {
        return 45
    }

}

fun main(args:Array<String>){
    //建立一個Printer對象,當成Output使用
    var o:Outputable=Printer()
    o.getData("輕量級javaEE企業應用實戰")
    o.getData("瘋狂java講義")
    o.out()
    o.getData("瘋狂安卓講義")
    o.getData("瘋狂ajax講義")
    o.out()
    //調用Outputable接口中定義的非抽象方法
    o.print("孫悟空","豬八戒","白骨精")
    o.test()
    //建立一個printer對象,當成product使用
    val p:Printer=Printer()
    println(p.getProductTime())
    //所有接口類型的應用變量都可以直接指派給Any類型的變量
    val obj:Any=p

}
           

列印:

列印機列印:輕量級javaEE企業應用實戰

列印機列印:瘋狂java講義

列印機列印:瘋狂安卓講義

列印機列印:瘋狂ajax講義

孫悟空

豬八戒

白骨精

接口中test()方法

45

19、接口和抽象類

同java。一樣一樣的

繼續閱讀