類(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)
一個類可以包含:
- 構造器與初始化塊
- 函數Functions
- 屬性Properties
- 嵌套和內部類Nested and Inner Classes
- 對象聲明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
修飾,向Base
的nv()
方法,在子類中聲明一個同樣的方法簽名將是非法的,無論子類中的方法是否被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#調用靜態方法相同的語法:使用類名作為限定符來訪問其成員。