Kotlin類與對象篇(1)--類與繼承(Inheritance)


歡迎關注 二師兄Kotlin
轉載請注明出處 二師兄kotlin


對象

Kotlin中對象使用 class關鍵字來進行聲明:

class Invoice {
}

類的聲明由三部分組成:類名、類頭(具體化的類型參數、主構造函數等)以及類體,被一堆花括號包圍{}( curly braces)。類頭和類體均是可選的。如果類沒有類體,花括號也可以被刪掉。

class Empty

構造函數(Constructors)

Kotlin中的類可以包含 一個主構造函數和多個次構造函數。而主構造函數是類頭的一部分:緊隨在類名之后(類型參數可選)。

class Person constructor(firstName: String) {
}

主構造函數不能包含任何代碼。初始化代碼必須放置在 初始化塊(initializer blocks)中,通過關鍵字init作為前綴。

在初始化一個實例的過程中,初始化塊按照出現在類體中的順序依次執行,可以和屬性初始化進行穿插。

class InitOrderDemo(name: String) {
    val firstProperty = "First property: $name".also(::println)

    init {
        println("First initializer block that prints ${name}")
    }

    val secondProperty = "Second property: ${name.length}".also(::println)

    init {
        println("Second initializer block that prints ${name.length}")
    }
}

注意主構造函數的參數可以在初始化塊中使用。同樣的也可以被用來進行屬性的初始化聲明。

class Customer(name: String) {
    val customerKey = name.toUpperCase()
}

實際上,為了在主構造函數中聲明屬性并且初始化他們,Kotlin提供了一種簡便的語法:

class Person(val firstName: String, val lastName: String, var age: Int) {
    // ...
}

和正常屬性非常一樣的方法,在首要構造器中的屬性可以使可變的 (var)或者只讀的 (val)。

如果構造器有注解或者可見性修飾符,關鍵字constructor就是必須的,并且修飾符需要在該關鍵字之前:

class Customer public @Inject constructor(name: String) { ... }

想了解更多細節,可以看Visibility Modifiers.

次構造函數

類也可以聲明次要構造器,次要構造器以constructor作為前綴:

class Person {
    constructor(parent: Person) {
        parent.children.add(this)
    }
}

如果類有首要構造器,每個次要構造器都需要去代理首要構造器,直接代理或間接通過另一個次要構造器去代理。代理同類的另一個構造器,需要使用關鍵字this

class Person(val name: String) {
    constructor(name: String, parent: Person) : this(name) {
        parent.children.add(this)
    }
}

注意:在初始化代碼塊的代碼高效地成為首要構造器的一部分。首要構造器的代理會作為次要構造器的第一行語句執行,所以在所有初始化代碼塊的代碼都在次要構造器之前執行。甚至,如果類沒有首要構造器,代理都會隱式地發生,初始化代碼塊依舊會執行:

class Constructors {
    init {
        println("Init block")
    }

    constructor(i: Int) {
        println("Constructor")
    }
}

如果,一個非抽象類沒有聲明任何構造器(包括首要和次要),類仍然會生成一個沒有參數的首要構造器。該構造器的可見性是public、如果你不想你的類有一個public構造器,你需要聲明一個空的首要構造器,并使用非默認可見性修飾符:

class DontCreateMe private constructor () {
}

NOTE:注意: 在JVM中,如果首要構造器的所有參數都有默認數值,編譯器將會生成一個額外的無參數的構造器,該構造器會使用默認數值。這樣使得更容易在Kotlin中使用庫,例如Jackson或者JPA(通過無參數構造器創建類的實例)

class Customer(val customerName: String = "")

創建類的實例

為了創建類的實例,我們將如調用普通函數一樣調用構造器:

val invoice = Invoice()
val customer = Customer("Joe Smith")
  • 注意Kotlin中不使用關鍵字new
    嵌套類、內部類、匿名內部類的創建在嵌套類中Nested classes
    進行介紹。

類的成員

類可以包含:

  • 構造函數和初始化塊
  • 函數
  • 屬性
  • 嵌套類和內部類
  • 對象聲明

繼承

在Kotlin中所有類都有一個共同的超類Any, 該超類是默認的,不需要超類的聲明:

class Example // 隱式繼承自Any

Any不是java.lang.Object;特別地,它除了equals(), hashCode() and toString()沒有其他任何成員。請參考Java interoperability
(Java互通性)章節。

為了聲明一個顯式的超類,我們將類型放置在類頭后的冒號后面:

open class Base(p: Int)
class Derived(p: Int) : Base(p)

如果類(子類)沒有首要構造器,每個次要構造器必須使用關鍵字super初始化基本類型(超類),或者代理給另一個完成該任務的構造器。注意,在這種情況下,不同的次要構造器可以調用基本類型(超類)的不同的構造器:

class MyView : View {
    constructor(ctx: Context) : super(ctx)

    constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
}

open注解在類中是與java中的final相反的內容:open允許其他類繼承自該類。默認的,在Kotlin中所有的類都是final(重點),這對應于Effective Java
, Item 17: Design and document for inheritance or else prohibit it.

重寫方法

正如我們之前提到的,我們堅持在Kotlin中讓事情都是顯式的。不像Java,Kotlin對于覆蓋的成員(這些成員需要有open修飾符)需要顯式的注釋:

open class Base {
    open fun v() {}
    fun nv() {}
}
class Derived() : Base() {
    override fun v() {}
}

override 注解需要修飾Derived.v(). 如果該注解遺漏,編譯器就會報錯。如果一個函數沒有open注解,類似Base.nv(), 在子類中聲明相同簽名的方法就是非法的,無論是否有overide。在一個final類中(即,一個沒有open注解的類), 禁止擁有open的成員(open修飾符無效).

overide標記的成員其本身就是open的,即,該成員可以在子類被重寫。如果你想禁止“再重寫”(重寫父類的方法繼續被子類重寫),可以使用final關鍵字:

open class AnotherDerived() : Base() {
    final override fun v() {}
}

屬性重寫

與方法重寫類似,在父類中已經聲明的屬性,如果要在一個派生類(derived class)中再次聲明, 則必須使用override打頭,而且他們必須類型可以兼容。一個擁有初始化或者getter方法的屬性都可以去重寫舊屬性。

open class Foo {
    open val x: Int get() { ... }
}

class Bar1 : Foo() {
    override val x: Int = ...
}

你可以用一個var屬性去重寫一個val屬性,但是,但是反過來則不行(vice versa)。這樣被允許的原因是一個val屬性本質上是聲明了一個getter方法,所以用var來重寫相當于在派生類中給他添加了一個setter方法。

注意你可以使用override關鍵字作為主構造函數屬性聲明的一部分。

interface Foo {
    val count: Int
}

class Bar1(override val count: Int) : Foo

class Bar2 : Foo {
    override var count: Int = 0
}

調用父類實現

派生類中的代碼通過super關鍵字調用父類的方法和屬性訪問器實現:

open class Foo {
    open fun f() { println("Foo.f()") }
    open val x: Int get() = 1
}

class Bar : Foo() {
    override fun f() { 
        super.f()
        println("Bar.f()") 
    }
    
    override val x: Int get() = super.x + 1
}

在一個內部類當中,訪問外部類的超類需要使用 標記了外部類類名的super關鍵字: super@Outer:

class Bar : Foo() {
    override fun f() { /* ... */ }
    override val x: Int get() = 0
    
    inner class Baz {
        fun g() {
            super@Bar.f() // Calls Foo's implementation of f()
            println(super@Bar.x) // Uses Foo's implementation of x's getter
        }
    }
}

重寫規則

在Kotlin中,繼承的實現被這樣一條規則所限制:如果一個類繼承了他的多個超類中的同一個函數的多個實現,那么他必須重寫這個函數且提供自己的實現(比如,使用所繼承實現中的某一個實現)。為了表示你到底是用了哪個超類中哪個實現,我們可以使用 尖括號(angle brackets)包圍的超類類名來標記super。比如,super<Base>

open class A {
    open fun f() { print("A") }
    fun a() { print("a") }
}

interface B {
    fun f() { print("B") } // interface members are 'open' by default
    fun b() { print("b") }
}

class C() : A(), B {
    // The compiler requires f() to be overridden:
    override fun f() {
        super<A>.f() // call to A.f()
        super<B>.f() // call to B.f()
    }
}

繼承AB沒有問題,而且對于在繼承類C中對于函數a()b()的實現也沒有問題。但是對于函數f(),對于C來說我們有兩個實現,所以我們必須去重寫f()并且提供自己的實現來消除歧義(eliminates the ambiguity)。

抽象類

類和他的一些成員可以聲明成abstract。一個抽象成員不可以包含實現。注意我們不需要用open來標注一個抽象類或者他的函數,默認就是open(it goes without saying 不用說)。

我們可用一個抽象的成員來重寫一個非抽象、但開放的成員。

open class Base {
    open fun f() {}
}

abstract class Derived : Base() {
    override abstract fun f()
}

友元對象(Companion Objects)

在Kotlin中,不想java或者C#,類沒有靜態方法。大多數情況下,更推薦使用 包級方法(package-level functions )來替代。

如果你需要寫這樣一個函數,他可以訪問一個類的內部但并非通過類對象實例(比如,一個工廠方法),你可以把它寫成一個object declaration成員放在類中。

更具體地說(Even more specifically ),如果你在類中聲明了一個

companion object,你將可以實現像調用Java/C#中靜態方法那樣的調用語法來調用companion object的成員,只需要類名作為標識。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,936評論 6 535
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,744評論 3 421
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,879評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,181評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,935評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,325評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,384評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,534評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,084評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,892評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,067評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,623評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,322評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,735評論 0 27
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,990評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,800評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,084評論 2 375

推薦閱讀更多精彩內容