類和對象
類和繼承
類
Kotlin中使用關鍵字class
聲明類
class Person{
}
類的聲明由類名、類頭(指定類的類型參數、主構造函數等)和類體(后面花括號包圍的部分);如果一個類沒有類體,可以省略花括號。
class Person
構造函數
在Kotlin中一個類可以有一個主構造函數和一個或多個次構造函數。主構造函數是類頭的一部分:它跟在類名(和可選的類型參數)后。
class Person constructor(firstName: String){
}
如果主構造函數沒有任何的注解和可見性修飾符,可以省略constructor
關鍵字。
class Person(firstName: String){
}
主構造函數不包含任何代碼。初始化代碼可以放在以init
關鍵字作為前綴的初始化塊(initializer block)中:
class Customer(name: String) {
init {
logger.info("Customer initialized with value ${name}")
}
}
注意主構造函數的參數可以在初始化塊中使用。也可以在類體內聲明屬性時,在屬性的初始化器中使用:
class Customer(name: String){
init{
logger.info("Customer initialized with value ${name}") //在初始化塊中使用主構造函數的參數
}
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){
//do some thing
}
次構造函數
類也可是聲明前綴有constructor
的次構造函數:
class Person{
constructor(parent: Person){
parent.chilren.add(this)
}
}
如果類有主構造函數,每個次構造函數都需要委托給主構造函數,可以直接委托或者通過別的次構造函數間接委托。委托到同一個類的另一個構造函數用this
關鍵字即可:
class Person(val name: String){
constructor(name: String,parent: Person) : this(name){
parent.children.add(this)
}
}
如果一個類非抽象并且沒有聲明任何(主或次)構造函數,它會默認生成一個無參的主構造函數。并且這個默認無參構造函數的可見性為public
。如果你不希望你的類有一個公有的構造函數,你需要聲明一個帶飛默認可見性的空主構造函數:
class DontCreatMe private constructor(){
}
在JVM上,如果主構造函數的所有參數都有默認值,編譯器會生成一個額外的無參構造函數,它將使用默認值,這使得Kotlin更易于使用像Jackson或JPA這樣通過無參構造函數創建類的實例的庫。
class Customer(val customerName: String = "")
創建類的實例
要創建類的實例,我們只需要像調用普通函數一樣調用構造函數:
val user = User()
val customer = Customer("Jon Smith")
可以發現Kotlin中創建類的實例,是不需要
new
關鍵字的,Kotlin中也沒有這個關鍵字
類成員
- 構造函數和初始化塊
- 函數
- 屬性
- 嵌套類和內部類
- 對象聲明
繼承
在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)
如果子類有一個主構造函數,其基類可以并且必須用子類主構造函數的參數就地初始化。
若子類沒有主構造函數,那么每個次構造函數必須使用super
關鍵字初始化其基類型,或委托另一個構造函數間接做到這一點。注意,在這種情況下,不同的次構造函數可以調用基類型的不同的構造函數
class MyView : View{
constructor(ctx: Context) : super(ctx)
constructor(ctx: Context,attrs: AttributeSet):super(ctx,attrs)
}
在Kotlin中,open
標注與Java
中的final
相反,它標注的類才允許被其他類繼承。默認情況下,在Kotlin中所有的類都是final
的,這正對應了Effective Java
中的要么為繼承而設計,并提供文檔說明,要么禁止繼承。
覆蓋方法
在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覆蓋
覆蓋規則
如果一個類從它的直接超類繼承相同成員的多個實現,那么這個類必須必須覆蓋這個成員并提供自己的實現(或者用繼承來的其中之一)。為了表示采用從哪個超類中繼承的實現,我們使用super<Base>
表示:
open class A {
open fun f() {
print("A")
}
fun a() {
print("a")
}
}
interface B {
fun f() {
print("B")
} // 接口成員默認就是“open”的,并且可以有實現
fun b() {
print("b")
}
}
class C() : A(), B {
// 編譯器要求覆蓋 f():
override fun f() {
super<A>.f() // 調用 A.f()
super<B>.f() // 調用 B.f()
}
}
類C
同時繼承了兩個不同f()
實現,所以必須在C
中覆蓋f()
并且提供自己的實現來消除歧義。
抽象類
類和其中某些成員可以聲明為abstract
的。抽象成員在本類中可以不實現。抽象類默認是開放的,不需要用open
關鍵字標注。并且我們可以用一個抽象成員覆蓋一個非抽象的開放成員
open class Base{
open fun f(){}
}
abstract class Derived : Base(){
override abstract fun f()
}
伴生對象
在Kotlin中沒有靜態方法。在大多數情況下,它建議采用包級函數(在類外面與包在一個級別)。
如果你需要寫一個無需類的實例來調用,但需要訪問類內部的的函數,你可以把它寫成該類內對象聲明中的一員,而不是類本身的成員。
這里說的類內聲明的對象,就是伴生對象,伴生對象內聲明的成員只需要類名作為限定符就可以訪問,語法與Java/C#中調用靜態方法一樣。
封閉類或密封類
用來限制類的繼承,可以限制給定的某個對象只可能是某些指定的類型之一。有點像枚舉。枚舉類的值是有限的,每個枚舉值常數都代表枚舉類的一個實例,而密封類允許的子類類型是有限的,但每個子類的實例數并未做限制。
如何聲明密封類?
- 使用
sealed
修飾符放在class
關鍵字之前; - 子類聲明必須嵌套在密封類聲明部分之內;
如:
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()
}
是否還有其他更有用的用途呢?