Kotlin學習之-5.1 類和繼承

Kotlin學習之-5.1 類和繼承

Kotlin中類定義使用關鍵字class

class Invoice {
}

定義一個類需要包括類名,類頭(包括它的類型參數,主構造函數等等)和類主體包含在成對的花括號。 類頭和類主體是可選的, 如果類沒有類主體,那麼花括號也可以省略

class Empty

構造函數

Kotlin中的類可以有一個主要的構造函數和多個次要構造函數。主要構造函數是類頭的一部分,它直接跟在類名後面,還有可選的類型參數。

class Person constructor(firstName: String) {
}

如果主要構造函數沒有任何註解或者可見性修飾符,構造函數關鍵字constructor可以省略

class Person(firstName: String) {
}

如果主要構造函數不包含任何代碼。初始化代碼可以被放置在初始化塊語句中,初始化塊語句的前面有init關鍵字:

class Custom(name: String) {
    init {
        logger.info("Customer initialized with value $name")
    }
}

注意主要構造函數的參數可以在初始化塊語句中使用。它們也可以在類主體中屬性初始化語句中使用。

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

實際上,在主要構造函數中定義屬性並初始化它們,Kotlin有種簡潔的寫法:

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

和普通屬性差不多,定義在主要構造函數中的屬性也可以是可變的var 或者只讀的val

如果構造函數有註解或者可見性描述符,那麼關鍵字constructor不能被省略,並且在描述符後面。

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

次要構造函數

類也可以定義次要構造函數,次要構造函數要使用關鍵字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)
    }
}

如果一個非抽象類沒有定義任何主要或者次要構造函數,它將會生成一個沒有參數的主要構造函數。這個主要構造函數是公共可見的。如果不想讓一個類有公共的構造函數,你需要定義一個私有的空的主要構造函數

calss DontCreateMe private custructor() {
}

注意在JVM上,如果一個主要構造函數的所有參數都有默認值,那麼編譯器會生成一個附加的沒有參數的構造函數,這個構造函數會使用這些默認值。 這樣使得像Jackson 或者JPA等這些第三方庫可以更容易的創建一個對象。

創建一個類的對象

創建一個類的對象,我們就像調用普通函數一樣調用它的構造函數就可以。

val invoice = Invoice()
val customer = Customer("Jack Chanson")

注意,Kotlin不支持new 關鍵字

類成員

類成員包括:

  • 構造函數和初始化塊代碼
  • 函數
  • 屬性
  • 內嵌類’Nested and Inner Classes’
  • 對象定義

繼承

所有Kotlin中的類都有一個共有的父類Any, 這是默認的父類,不需要聲明。

class Example // 隱式的繼承了Any

Any 不是一個java.lang.Object;特別地,它只有equals(), hashCode(), toString()這幾個方法。

要顯式地定義一個父類,我們在類頭的類型後面加上冒號,然後跟着父類

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

如果類有一個主要構造函數,那麼基類必須被定義在那裏,並使用主要構造函數的參數
如果類沒有主要構造函數,那麼每個次要構造函數必須使用關鍵字super來初始化基類,或者代理至另外一個使用了關鍵字super 初始化過基類的構造函數。 注意在這種情況下不同的次要構造函數可以調用基類的不同的構造函數。

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

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

關鍵字open 的作用和Java中的final正好相反,它允許其他類繼承這個類。 默認情況下,所有的Kotlin類都是final的,不可繼承的。這樣符合《Effective Java》中第17條的建議。

複寫方法

如上所述,我們堅持在Kotlin中寫的簡單明瞭。不像在Java,Kotlin需要顯式地給可以被複寫的成員寫上註解。

open class Base {
    open fun v() {}
    fun nv() {}
}

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

override註解對於方法Derived.v()來說是必須的。如果這個缺失了,編譯器會編譯失敗。 如果一個方法沒有open 註解,像例子中的Base.nv(), 那麼在子類中定義一個同樣簽名的方法是不允許的,不管有沒有override註解。 在一個final的類中,open 的成員是禁止的。

一個成員標記了override它自己是open的, 例如它可以被子類複寫。 如果想要禁止再次複寫這個成員,可以使用final關鍵字修飾。

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

複寫屬性

複寫屬性和複寫方法的方式很類似,定義在父類的屬性想要在子類中被複寫,必須有修飾符override, 並且他們必須要有能夠兼容的類型。每個定義的屬性都可以被一個有初始化器的屬性或者一個有getter()方法的屬性複寫。

open class Foo {
    open val x: Int get {}
}

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

可以使用一個變量var來繼承一個常量val, 單反過來不行。這是因爲常量val屬性其實會定義一個getter方法,並且把這個屬性複寫成變量var會在子類中多定義一個setter方法。

注意可以使用override關鍵字作爲主要構造函數中屬性定義的一部分。

interface Foo {
    val count: Int
}

class Bar(override val count: Int) : Foo

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

複寫規則

Kotlin中,實現繼承要按照如下規則進行:如果一個類從父類中繼承了一個成員的多個實現,它必須複寫這個成員並且提供它自己的實現(也可是使用其中一個繼承的)。爲了表示是從哪個父類中繼承的實現,我們使用super關鍵字加上尖括號中的父類名來表示super<Base>

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

interface B {
    fun f() { print("B") }
    fun b() { print("b") }
}

class C() : A(), B {
    // 強制要求函數f()被複寫
    override fun f() {
        super<A>.f() // 調用A 的f方法
        super<B>.f() // 調用B 的f方法
    }
}

可以同時從A和B中繼承,並且對於a() 和b() 來說是沒有問題的,因爲C在類中僅繼承了一個實現。但是對於函數f() 來說,就繼承了2個實現,因此我們必須複寫C 中的f() 方法,並且提供自己的實現來消除歧義。

抽象類

一個類和它的成員可以被定義成抽象的abstract, 一個抽象成員不需要在當前的類中實現。注意我們不需要標註一個抽象的類或者方法是公有open 的,抽象的類或者成員自動是公有open 的。

我們可以用一個抽象成員來複寫一個非抽象的公有成員

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

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

夥伴對象Companion Object

在Kotlin中,不像Java和C#, 類沒有靜態成員。 在大多數情況下,建議使用簡單的包級別的函數來代理。
如果需要寫一個函數,讓它在訪問的時候無需持有該類的對象,但還能夠訪問類的內部信息(例如一個工廠方法),可以在類的內部定義一個對象作爲成員來實現。

更具體的說,可以在類裏定義一個夥伴對象,這樣就可以使用類名來訪問類的成員,這樣就和Java/C#中的靜態方法是一樣的了。

class MyClass {
    companion object Factory {
        fun create(): MyClass = MyClass()
    }
}

val instance = MyClass.create()

PS,我會堅持把這個系列寫完,有問題可以留言交流,也關注專欄Kotlin for Android Kotlin安卓開發

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