類
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)對象聲明中的一員踩验。