Kotlin類和繼承
類
Kotlin中的類是使用class
關鍵字來聲明:
class Invoice {
}
類聲明由類名,類頭(指定類型參數,主構造函數等)和類體組成,由大括號括起來。類頭和類主體都是可選的; 如果類沒有主體,可以省略花括號。如下 -
class Empty
構造函數
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
)或只讀(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)
}
}
如果一個非抽象類沒有聲明任何構造函數(主或輔助),那麼它將不使用參數來生成主構造函數。 構造函數的可見性將是公開的。 如果不希望類具有公共構造函數,則需要聲明具有非默認可見性的空主構造函數:
class DontCreateMe private constructor () {
}
注意:在JVM上,如果主構造函數的所有參數都具有默認值,編譯器將生成一個額外的無參數構造函數,它將使用默認值。 這使得更容易使用Kotlin與諸如Jackson或JPA的庫,通過無參數構造函數創建類實例。
class Customer(val customerName: String = "")
創建類的實例
要創建一個類的實例,需要調用類的構造函數,就像它是一個常規函數一樣:
val invoice = Invoice()
val customer = Customer("Joe Minsu")
請注意,Kotlin創建對象並不使用
new
關鍵字。
在嵌套類中描述了創建嵌套,內部和匿名內部類的實例。
類成員
類可以包含 -
- 構造函數和初始化程序塊
- 函數
- 屬性
- 嵌套和內部類
- 對象聲明
繼承
Kotlin中的所有類都有一個通用的超類:Any
,這是一個沒有父類型的類的默認超類。
class Example // Implicitly inherits from Any
Any
不是java.lang.Object
; 特別地要說明的是,除了equals()
,hashCode()
和toString()
之外,它不具有其它任何成員函數。有關更多詳細信息,請參閱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的最終結果相反:它允許其他人繼承這個類。 默認情況下,Kotlin中的所有類都是final
,它對應於有效Java用法,設計和繼承的文檔或者禁止它。
重載方法
正如前面提到的,與Java不同的是,Kotlin需要對可覆蓋成員進行顯式註釋(稱之爲open
)和覆蓋:
open class Base {
open fun v() {}
fun nv() {}
}
class Derived() : Base() {
override fun v() {}
}
Derived.v()
需要覆蓋(override
)註釋。 如果缺少(override
)註釋,編譯器會抱錯。 如果在一個函數上沒有open
註釋,如在Base.nv()
中,在子類中聲明一個具有相同簽名的方法是非法的,無論是否有覆蓋(override
)註釋還是沒有。 在final
類(例如不使用open
註釋的類)中,則禁止覆蓋成員。
標記爲覆蓋(override
)的成員本身是打開的,即它可以在子類中被覆蓋。 如果要禁止重新覆蓋,請使用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 Bar1(override val count: Int) : Foo
class Bar2 : Foo {
override var count: Int = 0
}
覆蓋規則
在Kotlin中,實現繼承由以下規則控制:如果類從其直接超類繼承同一成員的多個實現,則它必須覆蓋該成員並提供自己的實現(可能使用其中一個繼承)。 要表示從其繼承的實現的超類型,可在尖括號中使用超類型名稱超級限定,例如,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()
}
}
從B
繼承A
是沒有問題的,對a()
和b()
函數也沒有任何問題,因爲C
只繼承每個這些函數的一個實現。 但是對於f()
有兩個由C
繼承的實現,因此必須在C
中重寫f()
函數並提供自己的消除歧義的實現。
抽象類
一個類和其一些成員可以被聲明爲抽象。 抽象成員在其類中沒有實現。 請注意,不需要使用open
來註釋抽象類或函數。
可以用抽象來覆蓋一個非抽象的open
成員 -
open class Base {
open fun f() {}
}
abstract class Derived : Base() {
override abstract fun f()
}
伴隨對象
在Kotlin中,與Java或C#不同,類沒有靜態(static
)方法。 在大多數情況下,建議簡單地使用包級別的功能。
如果需要編寫一個可以調用的函數,而不需要一個類實例,但需要訪問一個類的內部(例如,一個工廠方法),則可以將其作爲對象聲明的一個成員編寫。
更具體地說,如果在類中聲明瞭一個伴隨對象,則可以使用類名作爲限定符的Java/C#中調用靜態方法相同的語法來調用其成員。