Kotlin面向對象之類與繼承(Classes and Inheritance)

類(Classes)

在Kotlin中,類使用class關鍵字聲明:

class Invoice {
}

類的聲明由類名,類頭(指定其類型參數,主構造函數等)和類體組成,類體由大括號括起來。類頭和類體都是可選的; 如果一個類沒有類體,則可以省略花括號,如下:

class Empty

構造器(Constructors)

在Kotlin中,一個類可以有一個主構造器和若干個副構造器。主構造器作為類頭的一部分,緊跟類名:

class Person constructor(firstName: String) {
}

如果主構造器沒有被其他注解或可見修飾符修飾,則constructor關鍵字可以省略:

class Person(firstName: String) {
}

主構造器不能包含任何的代碼片段。主構造器聲明的參數的的初始化工作可以放在初始化塊中完成,初始化塊由init關鍵字做為前綴:

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

主構造器的參數不僅可以在初始化代碼塊中初始化,它們也可以被用于類體中其他屬性的初始化:

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

事實上,在主構造器中聲明并初始化若干屬性,Kotlin有著更加簡潔的語法:

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

與常規屬性類似,主構造函數中聲明的屬性可以可變(var)的也可以是只讀(read-only)的。

若主構造器被注解或可見性操作符修飾,則constructor關鍵字不可省略,修飾符在constructor關鍵字的前面:

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

副構造器(Secondary Constructors)

類也可以通過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)
    }
}

如果一個非抽象類沒有聲明任何構造器,則系統將為其自動生成一個public的無參構造器。如果你不想讓你的類具有一個public的構造器,你需要手動聲明一個private的非默認主構造器:

class DontCreateMe private constructor () {
}

注意:在JVM虛擬機中,如果主構造器的所有參數都有默認值,編譯器將額外生成一個無參的構造器,該構造器將使用主構造器中的默認值。這使Kotlin的使用變的更加容易,在使用諸如Jackson或JPA的庫時,可以通過無參數構造函數創建類實例:

class Customer(val customerName: String = "")

創建類的實例(Creating instances of classes)

要創建一個類的實例,我們可以通過調用構造器的方式來完成,就像調用一個常規函數一樣:

val invoice = Invoice()

val customer = Customer("Joe Smith")

注意:Kotlin中沒有new關鍵字。

關于嵌套類、內部類以及匿名內部類的創建方式,在Nested Classes介紹。

類成員(Class Members)

一個類可以包含:

  1. 構造器與初始化塊
  2. 函數Functions
  3. 屬性Properties
  4. 嵌套和內部類Nested and Inner Classes
  5. 對象聲明Object Declarations

對于新知識點,將后面的內容中逐步介紹到。

繼承(Inheritance)

Kotlin中的所有類都有一個公用的基類:Any,該類是沒有顯式聲明繼承關系的類的父類:

class Example // Implicitly inherits from Any

Any類不是java.lang.Object類:事實上,Any類除了equals()、hashCode()、toString()方法室外沒有任何的其他成員。關于更多細節,參見這里

為了明確表達繼承關系,可以將父類型放置在類頭的最后邊,并以冒號分割:

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

Kotlin中,open注解的含義和Java中的final是相反的:open允許其他類作為該類的子類。默認地,Kotlin中的所有類(包括方法)都被隱式聲明為final。

方法重載(Overriding Methods)

如前所述,使用Kotlin應堅持顯式聲明(因為默認都是final)。因此,不像Java,Kotlin需要對可覆蓋成員進行顯式注解(我們稱之為open)并重寫:

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

Derived類的v()函數由override注解修飾。如果該方法沒有被該注解修飾,則編譯器將會報錯。如果一個方法沒有被open修飾,向Basenv()方法,在子類中聲明一個同樣的方法簽名將是非法的,無論子類中的方法是否被override修飾。在一個final類中,即沒有被open修飾的類,用open修飾該類的成員是被禁止的。

一個被override修飾的成員,默認是open的,它可以被子類重寫。如果你想禁止子類重寫該override方法,應使用final關鍵字顯式聲明:

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

重寫屬性(Overriding Properties)

屬性的重寫與方法的重寫非常類似,父類聲明的屬性,若在子類中被重新聲明,則應以override修飾,且它們必須互相兼容。每個被聲明的屬性可以通過屬性初始化或該屬性的getter方法被重寫:

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

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

可以將val屬性重寫為var屬性,反之則不行。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
}

重寫規則(Overriding Rules)

在Kotlin中,實現繼承需要遵循以下規則:如果一個類從其直接父類中繼承了同一成員的多個實現,則它必須覆蓋該成員并提供自己的實現(當然,也可以使用其中一個父類的實現)。為了在自己的實現中表示父類的實現,我們使用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()
    }
}

關于對A和B的繼承,我們對a()和b()沒有異議,因為C只繼承了每些個函數的一個實現。 但是對于f(),我們從C繼承了兩個實現,因此我們必須在C中重寫f(),并提供我們自己的實現來消除歧義。

抽象類(Abstract Classes)

一個類和它的成員可以被abstract關鍵字修飾。一個抽象成員在該類中不能有其實現。需要注意的是我們不必對一個抽象類或抽象方法聲明open,這是毫無疑問的。

在一個抽象類中,我們可以重寫一個非抽象的但open的方法(也就是將一個非抽象方法重寫為抽象方法):

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

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

伴生對象(Companion Objects)

Kotlin不像Java或C#,類沒有靜態方法。常見的,推薦僅適用包級函數來代替。

如果需要編寫一個不需要類實例就可以調用的函數,且需要訪問一個類的內部(例如,一個工廠方法),則可以將其作為類內部的Object Declarations成員。

更具體地說,如果您在類中聲明了一個伴生對象,則可以使用與在Java / C#調用靜態方法相同的語法:使用類名作為限定符來訪問其成員。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容