【基礎篇】Kotlin第四講-類、對象和接口

類是邏輯組織的基本單元,類含有以下成分:依賴包,類名,構造方法,屬性,成員方法,伴生對象,接口,父類等

類的構造方法的完整邏輯過程

先考慮主構造函數,當主構造函數不夠用時,再引入從構造函數。最初的主構造函數是這樣的:步驟1通過主構造函數的參數傳入數據,步驟2在類裏定義所需的屬性,步驟3在init代碼塊裏對屬性做初始化/賦值操作,這三個步驟分工明確,前後關聯,共同完成了一個類的構造。

爲了簡化,Kotlin支持步驟2和步驟1合併操作,在主構造函數參數前加var/val;

支持步驟3和步驟2合併,對定義的屬性直接賦值。支持步驟3和步驟2和步驟1合併操作,使用主構造函數的默認參數表達。

當然這些合併帶來簡便的同時,降低了構造的能力。

最初的狀態(步驟1,步驟2,步驟3都存在)

class User(_sickName: String){

    val sickName: String

    init {
        println("init")
        sickName = _sickName
    }
}

步驟3和步驟1合併的操作

open class User private constructor(val sickName: String = "Kotlin", var age: Int){

    init {
        println("init")
    }
}

步驟3,步驟2和步驟1的合併

class User(val sickName: String){

    init {
        println("init")
    }
}

**注意:**Java中需要重載構造方法的場景大多數都被Kotlin參數默認值和參數命名的語法特性涵蓋了

子類的構造方法

“`
open class AirBook(name: String, val year: Int, val size: Int) {

constructor(name: String, year: Int) : this(name, year, 22){
    println("constructor")
}

}

class MacBookPro: AirBook{

public constructor(name: String, year: Int) : super(name, year) {
}

public constructor(name: String, year: Int, size: Int) : super(name, year, size){
}

}

“`

子類在創建的時候,必須調用父類的構造函數,如果其父類還有父類,仍然要調用父類的父類的構造函數,直至頂層的基類。

open class AirBook(name: String, val year: Int, val size: Int){

    init {
    }

    constructor(name: String, year: Int) : this(name, year, 22){
    }
}

構造函數可以調用當前自身的構造函數,因爲自身的構造函數必然有至少一個調用了父類的構造函數。

屬性訪問器

在kotlin裏,一個屬性 = 字段 + 屬性訪問器;這裏的字段是Java裏的成員變量

屬性訪問器分爲讀訪問器getter,和寫訪問器setter

訪問器裏存在field字段,用來連接寫訪問器和讀訪問器,作爲兩者的通信橋樑

val修飾的屬性只有讀訪問器,var修飾的屬性既有有讀也有寫訪問器

Kotlin引入屬性訪問器後,凡事對屬性進行賦值操作,就會調用屬性的寫訪問器setter;讀取值的操作對應調用的是屬性的讀訪問器

那麼如果我們要自定義一個類似Java裏的setter方法,要怎麼做呢?

通過var變量的私有setter來實行

代碼如下:

class TableLamp(val name: String, lightness: Int){

    var lightness: Int = 1
    private set

    fun setupLightness(lightness: Int){
        this.lightness = lightness
        println("setupLightness lightness = $lightness")
    }
}

這樣對屬性lightness的修改,只能通過setupLightness方法。

修飾詞final, open, abstract相互影響和使用

Kotlin的函數,類默認是final的,如果想重寫函數,類有子類,則需使用open修飾。abstract修飾的函數和類,意味着有open的特性。這和Java的用法是一致的。

Kotlin自帶的特殊類

數據類,嵌套類,內部類,密封類的基本寫法這裏就略去不談。

數據類data

data修飾的類,必須有主構造函數,且主構造每個入參必須都val或var修飾

data修飾補充的方法裏的所用的變量取決於主構造函數的入參

嵌套類和內部類

定義在某個類內部並用inner修飾的類稱爲內部類

嵌套類可以類比Java的靜態內部類,Kotlin的內部類類比Java的內部類,他們含有外部類的引用。與Java內部類不同的是,Kotlin的內部類能對外部類的變量進行寫操作。

舉個例子

class TextView{
    var counter: Int= 0

    inner class Operator{
        fun calculate(){
            counter ++
        }
    }
}

內部類Operator的成員函數可以寫外部類的counter屬性,這在Java裏是做不到的。

密封類

sealed關鍵詞修飾的類,表達有限個子類。

sealed class Color constructor(val name: String)

class Red : Color("Red")

class Green : Color("Green")

class Blue : Color("Blue")

class Gray : Color("Gray")

上述寫法,編譯器就會知道,Color的子類的數量,此例Color一共只有四個子類,分別是:Red,Green, Blue, Gray。如果增加或刪除Color的子類,編譯器是能感知到的。密封類的子類可以作爲嵌套類,也可以寫在密封類所在的文件裏,但是不能寫在其他文件裏。

舉個和When搭配的應用例子:

fun testSealed(color: Color): Int =
        when (color) {
            is Red -> {
                println("Red")
                1
            }
            is Green -> {
                println("Green")
                2
            }
            is Blue -> {
                println("Blue")
                3
            }
            is GRAY ->{
                println("GRAY")
                4
            }
        }

密封類的意義:
如果不用密封類,使用when總是不得不添加一個默認分支。更重要的是,如果你添加了一個新的子類,編譯器並不能發現有地方改變了。如果你忘記了添加一個分支,就會選擇默認的選項,這可能導致潛在的bug。

使用密封類就能解決上述的問題。

Object的使用

聲明一個類並創建一個對應的實例。與類一樣,對象可以包含屬性,方法,初始化語句塊等的聲明。對象聲明可以繼承類和實現接口,尤其不包含狀態的時候很適用,也可以有擴展函數。

對象聲明

創建單一的實例,對象聲明在定義的時候就創建了,不需要在代碼的其他地方調用構造方法。

因此,對象聲明不允許有構造函數。

object CaseInsensitiveFileComparator : Comparator<String>{

    override fun compare(o1: String, o2: String): Int {
        return o1.compareTo(o2, true)
    }
}

Java調用上述代碼,則需要通過INSTANCE來調用,如下

 CaseInsensitiveFileComparator.INSTANCE.compare("abc", "abe");

伴生對象

創建單一的實例,可以實現Java裏訪問類的私有成員的靜態方法

在對象聲明的基礎上,使用companion關鍵字來標記,這樣做就獲得了直接通過容器類名稱來訪問這個對象的方法和屬性的能力。

舉個例子,實現CarFactory工廠

通過對象聲明的實現

class CarFactory{
    object Instance{
        fun newCar(name: String): Car{
            return Car(name)
        }

        fun newRedCar(name: String): Car{
            return Car(name, Red())
        }
    }
}

調用

  CarFactory.Instance.newCar("Mini Cooper")

通過伴生對象的實現

class CarFactory{
    companion object {
        fun newCar(name: String): Car{
            return Car(name)
        }

        fun newRedCar(name: String): Car{
            return Car(name, Red())
        }
    }
}

調用

CarFactory.newCar("Mini Cooper")

對象表達式

既然是表達式,就意味着有返回值

與Java的匿名內部類只能擴展一個類或者實現一個接口不同,Kotlin的匿名對象可以實現多個接口或者不實現接口。

與對象聲明不通,匿名對象不是單例的。每次對象表達式被執行都會創建一個新的對象實例。在對象表達式裏不經可以訪問創建它的函數中的變量,還可以修改變量的值

舉個例子

fun countClick(view: View){
    var clickCount = 0

    view.addClick(object : IClick{

        override fun onClick() {
            clickCount ++ 
        }
    })
}

小結

object對象在Kotlin中的意義:

  1. 實現Java裏靜態的功能,等效實現靜態調用
  2. 代替Java匿名內部類書寫

接口

interface IFocus {
    val focusName: String

    fun showOff(){
        println("IFocus foucusName length = ${focusName.length}")
    }

    fun onFocus()
}

IFocus接口聲明函數,讓子類實現,默認方法showOff,和抽象方法onFucus

class View : IFocus{

    override val focusName: String
        get() = "View"

    override fun onFocus() {
        showOff()
    } 
}

View實現IFocus接口,focusName由View來確定,獲取focusName邏輯寫在IFocus的默認方法裏。調用如下

val view = View()
view.onFocus()

調用返回內容

IFocus foucusName length = 4

其他

訪問權限修飾詞

protected在Kotlin和Java的區別
kotlin中protected只能是其子類和自身才能訪問;Java中則是同包下所有文件和不同包的子類能訪問

Kotiln訪問權限由小到大排列依次是:
private, protected, internal, public

Kotlin函數和屬性默認是public的,Java的默認是包級訪問範圍,即同一個包下的類能訪問。
Kotlin缺少包級別訪問控制,而多了一個模塊訪問範圍internal。internal表示同一個項目模塊下的類都能訪問

參考資料

Kotlin實戰

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章