類和對象
類和繼承
類
Kotlin中使用關(guān)鍵字class
聲明類
class Person{
}
類的聲明由類名、類頭(指定類的類型參數(shù)犀忱、主構(gòu)造函數(shù)等)和類體(后面花括號包圍的部分)得院;如果一個類沒有類體左电,可以省略花括號牌废。
class Person
構(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 block)中:
class Customer(name: String) {
init {
logger.info("Customer initialized with value ${name}")
}
}
注意主構(gòu)造函數(shù)的參數(shù)可以在初始化塊中使用懂从。也可以在類體內(nèi)聲明屬性時授段,在屬性的初始化器中使用:
class Customer(name: String){
init{
logger.info("Customer initialized with value ${name}") //在初始化塊中使用主構(gòu)造函數(shù)的參數(shù)
}
val customerKey = name.toUpperCase()//在屬性的初始化器中使用主構(gòu)造函數(shù)的慘是
}
其實,聲明屬性以及從主構(gòu)造函數(shù)初始化屬性番甩,Kotlin有更簡潔的語法:
class Person(
val firstName: String,
val lastName: String,
var age: Int) {
// ......
}
與普通在類內(nèi)部聲明的屬性一樣侵贵,主構(gòu)造函數(shù)中聲明的屬性也可以是可變(var
)或只讀(val
)。
如果主構(gòu)造函數(shù)有注解或可見性修飾符缘薛,則關(guān)鍵字constructor
不能省略模燥,并且修飾符在其前面:
class Customer public @Inject constructor(name: String){
//do some thing
}
次構(gòu)造函數(shù)
類也可是聲明前綴有constructor
的次構(gòu)造函數(shù):
class Person{
constructor(parent: Person){
parent.chilren.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){
constructor(name: String,parent: Person) : this(name){
parent.children.add(this)
}
}
如果一個類非抽象并且沒有聲明任何(主或次)構(gòu)造函數(shù)么翰,它會默認生成一個無參的主構(gòu)造函數(shù)牺汤。并且這個默認無參構(gòu)造函數(shù)的可見性為public
。如果你不希望你的類有一個公有的構(gòu)造函數(shù)浩嫌,你需要聲明一個帶飛默認可見性的空主構(gòu)造函數(shù):
class DontCreatMe private constructor(){
}
在JVM上檐迟,如果主構(gòu)造函數(shù)的所有參數(shù)都有默認值,編譯器會生成一個額外的無參構(gòu)造函數(shù)码耐,它將使用默認值追迟,這使得Kotlin更易于使用像Jackson或JPA這樣通過無參構(gòu)造函數(shù)創(chuàng)建類的實例的庫。
class Customer(val customerName: String = "")
創(chuàng)建類的實例
要創(chuàng)建類的實例骚腥,我們只需要像調(diào)用普通函數(shù)一樣調(diào)用構(gòu)造函數(shù):
val user = User()
val customer = Customer("Jon Smith")
可以發(fā)現(xiàn)Kotlin中創(chuàng)建類的實例,是不需要
new
關(guān)鍵字的,Kotlin中也沒有這個關(guān)鍵字
類成員
- 構(gòu)造函數(shù)和初始化塊
- 函數(shù)
- 屬性
- 嵌套類和內(nèi)部類
- 對象聲明
繼承
在Kotlin中所有類都有一個共同的超類Any
偷拔,所有類都直接或間接派生自這個類,類似Java
中的Object
class Example (){//默認派生自Any
}
但是厢绝,Any
并不是java.lang.Object
;它除了equals
、hasCode()
带猴、toString()
外沒有其它成員昔汉。
要繼承一個類,我們把超類放到派生類的類頭的冒號后面:
open class Base(p: Int)
class Derived(p: Int) : Base(p)
如果子類有一個主構(gòu)造函數(shù)拴清,其基類可以并且必須用子類主構(gòu)造函數(shù)的參數(shù)就地初始化靶病。
若子類沒有主構(gòu)造函數(shù),那么每個次構(gòu)造函數(shù)必須使用super
關(guān)鍵字初始化其基類型口予,或委托另一個構(gòu)造函數(shù)間接做到這一點娄周。注意,在這種情況下苹威,不同的次構(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中昆咽,open
標注與Java
中的final
相反,它標注的類才允許被其他類繼承牙甫。默認情況下掷酗,在Kotlin中所有的類都是final
的,這正對應(yīng)了Effective Java
中的要么為繼承而設(shè)計窟哺,并提供文檔說明泻轰,要么禁止繼承。
覆蓋方法
在Kotlin中力求清晰顯式且轨。與Java不同浮声,Kotlin中需要顯式標注可以被覆蓋的成員(或稱開放的成員)和覆蓋后的成員:
open class Base {
open fun v(){} //可以被復寫的成員用open標注
fun nv(){}
}
class Derived() : Base(){
final override fun v(){} //復寫后的成員用override標注,默認override標注的成員也是開放的旋奢,如果想再次限制其被覆蓋可以顯式的用final標注
}
覆蓋屬性
屬性覆蓋和方法覆蓋類似泳挥;在子類中聲明超類中的同名屬性就是覆蓋超類中的屬性,并且必須以override
開頭作為標注至朗,并且它們的類型必須兼容屉符。每個聲明的屬性可以由具有初始化器的屬性或者具有getter
方法的屬性覆蓋。
open class Foo{
open val x: Int锹引?= null
get(){//重寫get方法
field = 100
return field
}
}
class Bar1 : Foo(){
override val x: Int? = null
}
在Kotlin中矗钟,屬性都有默認的get
、set
方法嫌变,并且可以被重寫吨艇,如上面的例子。如果子類中覆蓋了父類中的屬性腾啥,那么在父類中重寫的被覆蓋屬性的get
方法东涡,在子類中是無效的冯吓。
val可以被var覆蓋
但是var不能被val覆蓋
覆蓋規(guī)則
如果一個類從它的直接超類繼承相同成員的多個實現(xiàn),那么這個類必須必須覆蓋這個成員并提供自己的實現(xiàn)(或者用繼承來的其中之一)软啼。為了表示采用從哪個超類中繼承的實現(xiàn)桑谍,我們使用super<Base>
表示:
open class A {
open fun f() {
print("A")
}
fun a() {
print("a")
}
}
interface B {
fun f() {
print("B")
} // 接口成員默認就是“open”的,并且可以有實現(xiàn)
fun b() {
print("b")
}
}
class C() : A(), B {
// 編譯器要求覆蓋 f():
override fun f() {
super<A>.f() // 調(diào)用 A.f()
super<B>.f() // 調(diào)用 B.f()
}
}
類C
同時繼承了兩個不同f()
實現(xiàn),所以必須在C
中覆蓋f()
并且提供自己的實現(xiàn)來消除歧義祸挪。
抽象類
類和其中某些成員可以聲明為abstract
的锣披。抽象成員在本類中可以不實現(xiàn)。抽象類默認是開放的贿条,不需要用open
關(guān)鍵字標注雹仿。并且我們可以用一個抽象成員覆蓋一個非抽象的開放成員
open class Base{
open fun f(){}
}
abstract class Derived : Base(){
override abstract fun f()
}
伴生對象
在Kotlin中沒有靜態(tài)方法。在大多數(shù)情況下整以,它建議采用包級函數(shù)(在類外面與包在一個級別)胧辽。
如果你需要寫一個無需類的實例來調(diào)用,但需要訪問類內(nèi)部的的函數(shù)公黑,你可以把它寫成該類內(nèi)對象聲明中的一員邑商,而不是類本身的成員。
這里說的類內(nèi)聲明的對象凡蚜,就是伴生對象人断,伴生對象內(nèi)聲明的成員只需要類名作為限定符就可以訪問,語法與Java/C#中調(diào)用靜態(tài)方法一樣朝蜘。
封閉類或密封類
用來限制類的繼承恶迈,可以限制給定的某個對象只可能是某些指定的類型之一。有點像枚舉谱醇。枚舉類的值是有限的暇仲,每個枚舉值常數(shù)都代表枚舉類的一個實例,而密封類允許的子類類型是有限的副渴,但每個子類的實例數(shù)并未做限制奈附。
如何聲明密封類?
- 使用
sealed
修飾符放在class
關(guān)鍵字之前煮剧; - 子類聲明必須嵌套在密封類聲明部分之內(nèi)桅狠;
如:
sealed class Expr {
class Const(val number:Double):Expr()
class Sum(val e1: Expr,val e2: Expr):Expr()
object NotAnumber : Expr()//這里聲明的是個單利類
}
用途:
在when
表達式中可以驗證分支語句覆蓋了所有條件,避免使用else
分支處理例外情況轿秧。
sealed class Expr {
class Const(val number:Double):Expr()
class Sum(val e1: Expr,val e2: Expr):Expr()
object NotAnumber : Expr()
}
是否還有其他更有用的用途呢?