Kotlin-類與繼承

Kotlin 中使用關(guān)鍵字 class 聲明類

class Invoice { /*……*/ }

類聲明由類名嘶伟、類頭(指定其類型參數(shù)荷憋、主構(gòu)造函數(shù)等)以及由花括號包圍的類體構(gòu)成塌鸯。類頭與類體都是可選的; 如果一個類沒有類體蝎毡,可以省略花括號。

class Empty

構(gòu)造函數(shù)

在 Kotlin 中的一個類可以有一個主構(gòu)造函數(shù)以及一個或多個次構(gòu)造函數(shù)氧枣。主構(gòu)造函數(shù)是類頭的一部分:它跟在類名(與可選的類型參數(shù))后沐兵。

class Person constructor(firstName: String) { /*……*/ }

如果主構(gòu)造函數(shù)沒有任何注解或者可見性修飾符,可以省略這個 constructor 關(guān)鍵字便监。

class Person(firstName: String) { /*……*/ }

主構(gòu)造函數(shù)不能包含任何的代碼扎谎。初始化的代碼可以放到以 init 關(guān)鍵字作為前綴的初始化塊(initializer blocks)中。

在實(shí)例初始化期間烧董,初始化塊按照它們出現(xiàn)在類體中的順序執(zhí)行毁靶,與屬性初始化器交織在一起:

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}")
    }
}

請注意,主構(gòu)造的參數(shù)可以在初始化塊中使用逊移。它們也可以在類體內(nèi)聲明的屬性初始化器中使用:

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

事實(shí)上预吆,聲明屬性以及從主構(gòu)造函數(shù)初始化屬性,Kotlin 有簡潔的語法:

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

與普通屬性一樣胳泉,主構(gòu)造函數(shù)中聲明的屬性可以是可變的(var)或只讀的(val)拐叉。
如果構(gòu)造函數(shù)有注解或可見性修飾符,這個 constructor 關(guān)鍵字是必需的扇商,并且這些修飾符在它前面:

class Customer public @Inject constructor(name: String) { /*……*/ }

次構(gòu)造函數(shù)

類也可以聲明前綴有 constructor的次構(gòu)造函數(shù):

class Person {
    var children: MutableList<Person> = mutableListOf()
    constructor(parent: Person) {
        parent.children.add(this)
    }
}

如果類有一個主構(gòu)造函數(shù)凤瘦,每個次構(gòu)造函數(shù)需要委托給主構(gòu)造函數(shù), 可以直接委托或者通過別的次構(gòu)造函數(shù)間接委托案铺。委托到同一個類的另一個構(gòu)造函數(shù)用 this 關(guān)鍵字即可:

class Person(val name: String) {
    var children: MutableList<Person> = mutableListOf()
    constructor(name: String, parent: Person) : this(name) {
        parent.children.add(this)
    }
}

請注意蔬芥,初始化塊中的代碼實(shí)際上會成為主構(gòu)造函數(shù)的一部分。委托給主構(gòu)造函數(shù)會作為次構(gòu)造函數(shù)的第一條語句红且,因此所有初始化塊與屬性初始化器中的代碼都會在次構(gòu)造函數(shù)體之前執(zhí)行坝茎。即使該類沒有主構(gòu)造函數(shù),這種委托仍會隱式發(fā)生暇番,并且仍會執(zhí)行初始化塊:

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

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

如果一個非抽象類沒有聲明任何(主或次)構(gòu)造函數(shù)嗤放,它會有一個生成的不帶參數(shù)的主構(gòu)造函數(shù)。構(gòu)造函數(shù)的可見性是 public壁酬。如果你不希望你的類有一個公有構(gòu)造函數(shù)次酌,你需要聲明一個帶有非默認(rèn)可見性的空的主構(gòu)造函數(shù):

class DontCreateMe private constructor () { /*……*/ }

注意:在 JVM 上恨课,如果主構(gòu)造函數(shù)的所有的參數(shù)都有默認(rèn)值,編譯器會生成 一個額外的無參構(gòu)造函數(shù)岳服,它將使用默認(rèn)值剂公。這使得 Kotlin 更易于使用像 Jackson 或者 JPA 這樣的通過無參構(gòu)造函數(shù)創(chuàng)建類的實(shí)例的庫。

class Customer(val customerName: String = "")

創(chuàng)建類的實(shí)例

要創(chuàng)建一個類的實(shí)例吊宋,我們就像普通函數(shù)一樣調(diào)用構(gòu)造函數(shù):

val invoice = Invoice()

val customer = Customer("Joe Smith")

注意 Kotlin 并沒有 new 關(guān)鍵字纲辽。

類成員

類可以包含:

繼承

在 Kotlin 中所有類都有一個共同的超類 Any,這對于沒有超類型聲明的類是默認(rèn)超類:

class Example // 從 Any 隱式繼承

Any 有三個方法:equals()璃搜、 hashCode() 與 toString()拖吼。因此,為所有 Kotlin 類都定義了這些方法这吻。
默認(rèn)的 toString() 打印所有屬性

默認(rèn)情況下吊档,Kotlin 類是最終(final)的:它們不能被繼承。 要使一個類可繼承唾糯,請用 open 關(guān)鍵字標(biāo)記它怠硼。

open class Base // 該類開放繼承

如需聲明一個顯式的超類型,請?jiān)陬愵^中把超類型放到冒號之后:

open class Base(p: Int)

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

1.如果派生類有一個主構(gòu)造函數(shù)移怯,其基類可以(并且必須) 用派生類主構(gòu)造函數(shù)的參數(shù)就地初始化香璃。
2.如果派生類沒有主構(gòu)造函數(shù),那么每個次構(gòu)造函數(shù)必須使用 super 關(guān)鍵字初始化其基類型舟误,或委托給另一個構(gòu)造函數(shù)做到這一點(diǎn)增显。 注意,在這種情況下脐帝,不同的次構(gòu)造函數(shù)可以調(diào)用基類型的不同的構(gòu)造函數(shù):

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

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

覆蓋方法

我們之前提到過,Kotlin 力求清晰顯式糖权。因此堵腹,Kotlin 對于可覆蓋的成員(我們稱之為開放)以及覆蓋后的成員需要顯式修飾符:

open class Shape {
    open fun draw() { /*……*/ }
    fun fill() { /*……*/ }
}

class Circle() : Shape() {
    override fun draw() { /*……*/ }
}

Circle.draw() 函數(shù)上必須加上 override 修飾符。如果沒寫星澳,編譯器將會報錯疚顷。 如果函數(shù)沒有標(biāo)注 open 如 Shape.fill(),那么子類中不允許定義相同簽名的函數(shù)禁偎, 不論加不加 override腿堤。將 open 修飾符添加到 final 類(即沒有 open 的類)的成員上不起作用。

標(biāo)記為 override 的成員本身是開放的如暖,也就是說笆檀,它可以在子類中覆蓋。如果你想禁止再次覆蓋盒至,使用 final 關(guān)鍵字:

open class Rectangle() : Shape() {
    final override fun draw() { /*……*/ }
}

覆蓋屬性

屬性覆蓋與方法覆蓋類似酗洒;在超類中聲明然后在派生類中重新聲明的屬性必須以 override 開頭士修,并且它們必須具有兼容的類型。 每個聲明的屬性可以由具有初始化器的屬性或者具有 get 方法的屬性覆蓋樱衷。

open class Shape {
    open val vertexCount: Int = 0
}

class Rectangle : Shape() {
    override val vertexCount = 4
}

你也可以用一個 var 屬性覆蓋一個 val 屬性棋嘲,但反之則不行。 這是允許的矩桂,因?yàn)橐粋€ val 屬性本質(zhì)上聲明了一個 get 方法沸移, 而將其覆蓋為 var 只是在子類中額外聲明一個 set 方法。

請注意侄榴,你可以在主構(gòu)造函數(shù)中使用 override 關(guān)鍵字作為屬性聲明的一部分雹锣。

interface Shape {
    val vertexCount: Int
}

class Rectangle(override val vertexCount: Int = 4) : Shape // 總是有 4 個頂點(diǎn)

class Polygon : Shape {
    override var vertexCount: Int = 0  // 以后可以設(shè)置為任何數(shù)
}

派生類初始化順序

在構(gòu)造派生類的新實(shí)例的過程中,第一步完成其基類的初始化(在之前只有對基類構(gòu)造函數(shù)參數(shù)的求值)牲蜀,因此發(fā)生在派生類的初始化邏輯運(yùn)行之前笆制。

open class Base(val name: String) {

    init { println("Initializing Base") }

    open val size: Int = 
        name.length.also { println("Initializing size in Base: $it") }
}

class Derived(
    name: String,
    val lastName: String,
) : Base(name.capitalize().also { println("Argument for Base: $it") }) {

    init { println("Initializing Derived") }

    override val size: Int =
        (super.size + lastName.length).also { println("Initializing size in Derived: $it") }
}

fun main() {
    println("Constructing Derived(\"hello\", \"world\")")
    val d = Derived("hello", "world")
}

Constructing Derived("hello", "world")
Argument for Base: Hello
Initializing Base
Initializing size in Base: 5
Initializing Derived
Initializing size in Derived: 10

這意味著,基類構(gòu)造函數(shù)執(zhí)行時涣达,派生類中聲明或覆蓋的屬性都還沒有初始化在辆。如果在基類初始化邏輯中(直接或通過另一個覆蓋的 open 成員的實(shí)現(xiàn)間接)使用了任何一個這種屬性,那么都可能導(dǎo)致不正確的行為或運(yùn)行時故障度苔。設(shè)計(jì)一個基類時匆篓,應(yīng)該避免在構(gòu)造函數(shù)、屬性初始化器以及 init 塊中使用 open 成員寇窑。

調(diào)用超類實(shí)現(xiàn)

派生類中的代碼可以使用 super 關(guān)鍵字調(diào)用其超類的函數(shù)與屬性訪問器的實(shí)現(xiàn):

open class Rectangle {
    open fun draw() { println("Drawing a rectangle") }
    val borderColor: String get() = "black"
}

class FilledRectangle : Rectangle() {
    override fun draw() {
        super.draw()
        println("Filling the rectangle")
    }

    val fillColor: String get() = super.borderColor
}

在一個內(nèi)部類中訪問外部類的超類鸦概,可以通過由外部類名限定的 super 關(guān)鍵字來實(shí)現(xiàn):super@Outer:

open class Rectangle {
    open fun draw() { println("Drawing a rectangle") }
    val borderColor: String get() = "black"
}

class FilledRectangle: Rectangle() {
    override fun draw() { 
        val filler = Filler()
        filler.drawAndFill()
    }

    inner class Filler {
        fun fill() { println("Filling") }
        fun drawAndFill() {
            super@FilledRectangle.draw() // 調(diào)用 Rectangle 的 draw() 實(shí)現(xiàn)
            fill()
            println("Drawn a filled rectangle with color ${super@FilledRectangle.borderColor}") // 使用 Rectangle 所實(shí)現(xiàn)的 borderColor 的 get()
        }
    }
}

fun main() {
    val fr = FilledRectangle()
        fr.draw()
}

覆蓋規(guī)則

在 Kotlin 中,實(shí)現(xiàn)繼承由下述規(guī)則規(guī)定:如果一個類從它的直接超類繼承相同成員的多個實(shí)現(xiàn)甩骏, 它必須覆蓋這個成員并提供其自己的實(shí)現(xiàn)(也許用繼承來的其中之一)窗市。 為了表示采用從哪個超類型繼承的實(shí)現(xiàn),我們使用由尖括號中超類型名限定的 super饮笛,如 super<Base>:

open class Rectangle {
    open fun draw() { /* …… */ }
}

interface Polygon {
    fun draw() { /* …… */ } // 接口成員默認(rèn)就是“open”的
}

class Square() : Rectangle(), Polygon {
    // 編譯器要求覆蓋 draw():
    override fun draw() {
        super<Rectangle>.draw() // 調(diào)用 Rectangle.draw()
        super<Polygon>.draw() // 調(diào)用 Polygon.draw()
    }
}

可以同時繼承 Rectangle 與 Polygon咨察, 但是二者都有各自的 draw() 實(shí)現(xiàn),所以我們必須在 Square 中覆蓋 draw()福青, 并提供其自身的實(shí)現(xiàn)以消除歧義摄狱。

抽象類

類以及其中的某些成員可以聲明為 abstract。 抽象成員在本類中可以不用實(shí)現(xiàn)无午。 需要注意的是媒役,我們并不需要用 open 標(biāo)注一個抽象類或者函數(shù)——因?yàn)檫@不言而喻。

我們可以用一個抽象成員覆蓋一個非抽象的開放成員

open class Polygon {
    open fun draw() {}
}

abstract class Rectangle : Polygon() {
    abstract override fun draw()
}

伴生對象

如果你需要寫一個可以無需用一個類的實(shí)例來調(diào)用宪迟、但需要訪問類內(nèi)部的函數(shù)(例如酣衷,工廠方法),你可以把它寫成該類內(nèi)對象聲明中的一員踩验。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鸥诽,一起剝皮案震驚了整個濱河市商玫,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌牡借,老刑警劉巖拳昌,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異钠龙,居然都是意外死亡炬藤,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進(jìn)店門碴里,熙熙樓的掌柜王于貴愁眉苦臉地迎上來沈矿,“玉大人,你說我怎么就攤上這事咬腋「牛” “怎么了?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵根竿,是天一觀的道長陵像。 經(jīng)常有香客問我,道長寇壳,這世上最難降的妖魔是什么醒颖? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮壳炎,結(jié)果婚禮上泞歉,老公的妹妹穿的比我還像新娘。我一直安慰自己匿辩,他們只是感情好腰耙,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著铲球,像睡著了一般沟优。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上睬辐,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天,我揣著相機(jī)與錄音宾肺,去河邊找鬼溯饵。 笑死,一個胖子當(dāng)著我的面吹牛锨用,可吹牛的內(nèi)容都是我干的丰刊。 我是一名探鬼主播,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼增拥,長吁一口氣:“原來是場噩夢啊……” “哼啄巧!你這毒婦竟也來了寻歧?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤秩仆,失蹤者是張志新(化名)和其女友劉穎码泛,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體澄耍,經(jīng)...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡噪珊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了齐莲。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片痢站。...
    茶點(diǎn)故事閱讀 38,716評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖选酗,靈堂內(nèi)的尸體忽然破棺而出阵难,到底是詐尸還是另有隱情,我是刑警寧澤芒填,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布呜叫,位于F島的核電站,受9級特大地震影響氢烘,放射性物質(zhì)發(fā)生泄漏怀偷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一播玖、第九天 我趴在偏房一處隱蔽的房頂上張望椎工。 院中可真熱鬧,春花似錦蜀踏、人聲如沸维蒙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽颅痊。三九已至,卻和暖如春局待,著一層夾襖步出監(jiān)牢的瞬間斑响,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工钳榨, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留舰罚,地道東北人。 一個月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓薛耻,卻偏偏與公主長得像营罢,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子饼齿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評論 2 350

推薦閱讀更多精彩內(nèi)容