歡迎關注 二師兄Kotlin
轉載請注明出處 二師兄kotlin
對象
Kotlin中對象使用 class
關鍵字來進行聲明:
class Invoice {
}
類的聲明由三部分組成:類名、類頭(具體化的類型參數、主構造函數等)以及類體,被一堆花括號包圍{}
( curly braces)。類頭和類體均是可選的。如果類沒有類體,花括號也可以被刪掉。
class Empty
構造函數(Constructors)
Kotlin中的類可以包含 一個主構造函數和多個次構造函數。而主構造函數是類頭的一部分:緊隨在類名之后(類型參數可選)。
class Person constructor(firstName: String) {
}
主構造函數不能包含任何代碼。初始化代碼必須放置在 初始化塊(initializer blocks
)中,通過關鍵字init
作為前綴。
在初始化一個實例的過程中,初始化塊按照出現在類體中的順序依次執行,可以和屬性初始化進行穿插。
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}")
}
}
注意主構造函數的參數可以在初始化塊中使用。同樣的也可以被用來進行屬性的初始化聲明。
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) { ... }
想了解更多細節,可以看Visibility Modifiers.
次構造函數
類也可以聲明次要構造器,次要構造器以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 Constructors {
init {
println("Init block")
}
constructor(i: Int) {
println("Constructor")
}
}
如果,一個非抽象類沒有聲明任何構造器(包括首要和次要),類仍然會生成一個沒有參數的首要構造器。該構造器的可見性是public、如果你不想你的類有一個public構造器,你需要聲明一個空的首要構造器,并使用非默認可見性修飾符:
class DontCreateMe private constructor () {
}
NOTE:注意: 在JVM中,如果首要構造器的所有參數都有默認數值,編譯器將會生成一個額外的無參數的構造器,該構造器會使用默認數值。這樣使得更容易在Kotlin中使用庫,例如Jackson或者JPA(通過無參數構造器創建類的實例)
class Customer(val customerName: String = "")
創建類的實例
為了創建類的實例,我們將如調用普通函數一樣調用構造器:
val invoice = Invoice()
val customer = Customer("Joe Smith")
- 注意Kotlin中不使用關鍵字
new
。
嵌套類、內部類、匿名內部類的創建在嵌套類中Nested classes
進行介紹。
類的成員
類可以包含:
- 構造函數和初始化塊
- 函數
- 屬性
- 嵌套類和內部類
- 對象聲明
繼承
在Kotlin中所有類都有一個共同的超類Any, 該超類是默認的,不需要超類的聲明:
class Example // 隱式繼承自Any
Any不是java.lang.Object
;特別地,它除了equals(), hashCode() and toString()沒有其他任何成員。請參考Java interoperability
(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中的final
相反的內容:open允許其他類繼承自該類。默認的,在Kotlin中所有的類都是final(重點),這對應于Effective Java
, Item 17: Design and document for inheritance or else prohibit it.
重寫方法
正如我們之前提到的,我們堅持在Kotlin中讓事情都是顯式的。不像Java,Kotlin對于覆蓋的成員(這些成員需要有open修飾符)需要顯式的注釋:
open class Base {
open fun v() {}
fun nv() {}
}
class Derived() : Base() {
override fun v() {}
}
override
注解需要修飾Derived.v()
. 如果該注解遺漏,編譯器就會報錯。如果一個函數沒有open
注解,類似Base.nv()
, 在子類中聲明相同簽名的方法就是非法的,無論是否有overide
。在一個final類中(即,一個沒有open
注解的類), 禁止擁有open的成員(open修飾符無效).
被overide
標記的成員其本身就是open的,即,該成員可以在子類被重寫。如果你想禁止“再重寫”(重寫父類的方法繼續被子類重寫),可以使用final
關鍵字:
open class AnotherDerived() : Base() {
final override fun v() {}
}
屬性重寫
與方法重寫類似,在父類中已經聲明的屬性,如果要在一個派生類(derived class)中再次聲明, 則必須使用override
打頭,而且他們必須類型可以兼容。一個擁有初始化或者getter方法的屬性都可以去重寫舊屬性。
open class Foo {
open val x: Int get() { ... }
}
class Bar1 : Foo() {
override val x: Int = ...
}
你可以用一個var
屬性去重寫一個val
屬性,但是,但是反過來則不行(vice versa)。這樣被允許的原因是一個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
}
調用父類實現
派生類中的代碼通過super
關鍵字調用父類的方法和屬性訪問器實現:
open class Foo {
open fun f() { println("Foo.f()") }
open val x: Int get() = 1
}
class Bar : Foo() {
override fun f() {
super.f()
println("Bar.f()")
}
override val x: Int get() = super.x + 1
}
在一個內部類當中,訪問外部類的超類需要使用 標記了外部類類名的super
關鍵字: super@Outer
:
class Bar : Foo() {
override fun f() { /* ... */ }
override val x: Int get() = 0
inner class Baz {
fun g() {
super@Bar.f() // Calls Foo's implementation of f()
println(super@Bar.x) // Uses Foo's implementation of x's getter
}
}
}
重寫規則
在Kotlin中,繼承的實現被這樣一條規則所限制:如果一個類繼承了他的多個超類中的同一個函數的多個實現,那么他必須重寫這個函數且提供自己的實現(比如,使用所繼承實現中的某一個實現)。為了表示你到底是用了哪個超類中哪個實現,我們可以使用 尖括號(angle brackets)包圍的超類類名來標記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
沒有問題,而且對于在繼承類C
中對于函數a()
和b()
的實現也沒有問題。但是對于函數f()
,對于C
來說我們有兩個實現,所以我們必須去重寫f()
并且提供自己的實現來消除歧義(eliminates the ambiguity)。
抽象類
類和他的一些成員可以聲明成abstract
。一個抽象成員不可以包含實現。注意我們不需要用open
來標注一個抽象類或者他的函數,默認就是open
(it goes without saying 不用說)。
我們可用一個抽象的成員來重寫一個非抽象、但開放的成員。
open class Base {
open fun f() {}
}
abstract class Derived : Base() {
override abstract fun f()
}
友元對象(Companion Objects)
在Kotlin中,不想java或者C#,類沒有靜態方法。大多數情況下,更推薦使用 包級方法(package-level functions )來替代。
如果你需要寫這樣一個函數,他可以訪問一個類的內部但并非通過類對象實例(比如,一個工廠方法),你可以把它寫成一個object declaration成員放在類中。
更具體地說(Even more specifically ),如果你在類中聲明了一個
companion object,你將可以實現像調用Java/C#中靜態方法那樣的調用語法來調用companion object
的成員,只需要類名作為標識。