繼承和重寫
kt 中使用 :(冒號) 代替 java 中的 extends 和 implements
重寫父類的方法時,必須使用 override 標識。
當有多個父類,且父類中的方法沖突時,可以通過 把父類放到 super<> 的尖括號中指定調(diào)用哪個父類中的方法。
class Impl:Itf,Itf2{
override fun test() {
super<Itf>.test() // 調(diào)用 Itf 接口中的 test 方法
super<Itf2>.test() // 調(diào)用 Itf 接口中的 test 方法
super.say() // 方法不沖突時,直接使用 super. 調(diào)用父類的方法
println("impl")
}
}
類的定義
使用 class 關鍵字定義一個類
class Demo2{
fun test() = println("demo2#test")
fun test2() = println("demo2#open")
}
kt 中的所有類都是 final ,不能被繼承;所有方法也都是 final 不能被重寫。
open 與 final
kt 中,類、方法默認都是 final。
可以使用 open 修飾符取消類、方法或?qū)傩缘?final 屬性。
繼承的方法、屬性也是 open,除非手動使用 final 進行修飾:
class Demo
open class Demo2{
fun test() = println("demo2#test")
open fun test2() = println("demo2#open")
}
open class Demo3:Demo2(){ // 可以繼成 Demo2,但不能集成 Demo
final override fun test2() { // 重寫 test2() ,但不能重寫 test
super.test2()
}
}
class Demo4:Demo3(){
// 由于 Demo3 將 test2() 聲明為 final 類型,所以該類中沒有可重寫的方法
}
構(gòu)造函數(shù)
kt 中的構(gòu)造函數(shù)分為 主構(gòu)造函數(shù),從構(gòu)造函數(shù)。主構(gòu)造函數(shù)在類的外部聲明,而從構(gòu)造函數(shù)在類的內(nèi)部聲明。
- 所有的從構(gòu)造函數(shù)使用 constructor 聲明。
如果沒有給一個類聲明任何構(gòu)造函數(shù),將會生成一個不做任何事情的默認構(gòu)造函數(shù)。
同時定義主構(gòu)造函數(shù)與從構(gòu)造函數(shù)時,從構(gòu)造函數(shù)必須調(diào)用到主構(gòu)造函數(shù)。如下,雖然主構(gòu)造函數(shù)沒有做任何操作,但從構(gòu)造函數(shù)最終必須要調(diào)用到主構(gòu)造函數(shù)。
構(gòu)造函數(shù)通過 this 相互調(diào)用。
class Child2(){
constructor(a:Int):this(a,1)
constructor(a:Int,b:Int):this()
}
主構(gòu)造函數(shù)與 init
主構(gòu)造函數(shù)定義在類的外面。
主構(gòu)造函數(shù)功能很單一,只能 為類定義和參數(shù)同名的屬性,并生成其對應的 getter/setter。如果需要在構(gòu)造函數(shù)中執(zhí)行一些初始化操作,需要使用 init 定義初始化語句塊。
初始化語句塊只能與主構(gòu)造函數(shù)一起使用。一個類中可以定義多個初始化語句塊。
-
init 塊中可以使用主構(gòu)造函數(shù)中的參數(shù)。如下面代碼中,可以在 init 塊中直接使用 name 參數(shù)。
// 定義主構(gòu)造函數(shù)
class Demo constructor(name:String){
val name:String
init {
this.name = name
}
}
上述代碼如下面的 java 代碼功能一樣,為類定義一個 name 屬性,同時將構(gòu)造函數(shù)中的 name 值賦值給 name 屬性:
class Demo{
public final String name;
Demo(String name){
this.name = name;
}
}
主構(gòu)造函數(shù)的演化
在不進行簡化的情況下,主構(gòu)造函數(shù)的定義如下:
// 定義構(gòu)造函數(shù),同時對主構(gòu)造函數(shù)進行修飾與使用注解
class Test @A private constructor(name:String){
lateinit var name_:String
init {
this.name_ = name
}
}
-
如果主構(gòu)造函數(shù)沒有被注解或者可見性修飾符修飾時,可以直接使用 constructor。如下:
class Test constructor(name:String){ lateinit var name_:String init { this.name_ = name } } -
當主構(gòu)造函數(shù)只有 constructor 時,可以省略 constructor 關鍵字。因此上面代碼可以簡寫如下:
class Test(name:String){ lateinit var name_:String init { this.name_ = name } } -
如果 init 代碼塊中只是為同名屬性賦值,可以省略 init 代碼塊:
class Test(name:String){ val name = name // 可以直接使用構(gòu)造函數(shù)中的參數(shù)為屬性賦值 } -
如果不需要為屬性自定義 getter/setter,可以將修飾屬性的 var/val 提到構(gòu)造函數(shù)中。此時會生成默認的 getter/setter
class Test(val name: String) -
主構(gòu)造函數(shù)中的屬性無法直接設置 getter/setter,需要用另外的屬性:
class Test(name:String,age:Int){ var name = name get() { println("getter") return "from getter" } }
從構(gòu)造函數(shù)
定義在類的內(nèi)部,而不是類名后面的構(gòu)造函數(shù)
大部分情況下,使用主構(gòu)造函數(shù)就夠了。之所以定義從構(gòu)造函數(shù),是為了能通過不同的方式初始化類。
```kotlin
class Demo{
val name:String
// 從構(gòu)造函數(shù)
constructor(name: String){
this.name = name
}
}
```
繼承時的構(gòu)造函數(shù)
同 java 一樣,子類的構(gòu)造函數(shù)必須調(diào)用到父類的構(gòu)造函數(shù)。
唯一原則:確保無論用戶調(diào)用哪個構(gòu)造函數(shù),它必須能調(diào)用到父類的構(gòu)造函數(shù)。
-
有主構(gòu)造函數(shù)時,從構(gòu)造函數(shù)不能直接調(diào)用父構(gòu)造函數(shù)。
open class Parent{ constructor(name: String) } class Child():Parent("x") class Child2:Parent{ constructor():super("xx") } class Child3():Parent(""){ constructor(name:String):this() }第一個類中,用戶調(diào)用主構(gòu)造函數(shù)時,可以調(diào)用到父類。
第二個類中,用戶調(diào)用從時,可以調(diào)用到父類。如果定義主構(gòu)造函數(shù),則會報錯。因為用戶調(diào)用主構(gòu)造函數(shù)時,無法調(diào)用到父類的構(gòu)造函數(shù)。
第三個類中,有主從。從只能調(diào)用主構(gòu)造函數(shù)。這是因為主構(gòu)造函數(shù)可能會定義一系列的變量,如果調(diào)用不到主,則這些變量無法初始化。
抽象類
定義方式與 java 一樣。
普通類默認是 final ,但抽象類始終是 open
抽象類中的非抽象函數(shù)默認是 final,抽象函數(shù)是 open。
abstract class Demo2{
fun test() = println("demo2#test") // 默認是 final,子類不能重寫 test()
open fun test2() = println("demo2#open") // 顯式使用 open 關鍵字
abstract fun test3() // 抽象函數(shù),默認是 open。被重寫后,默認也是 open
}
內(nèi)部類與嵌套類
kt 中直接將一個類定義在另一個類內(nèi)部,該類叫嵌套類。嵌套類不持有外部類的引用。相當于 java 中被 static 修飾的內(nèi)部類。
嵌套類使用 inner 修飾,則該類為內(nèi)部類,此時同普通的 java 內(nèi)部類??梢砸猛獠款惖某蓡T屬性、方法。
-
內(nèi)部類通過 this@Outer 獲取外部類的引用
class Student{ val name = "name" override fun toString(): String { return "toString" } class Inner{ fun test() = println("--") // 無法引用外部類 } // 定義成內(nèi)部類 inner class InnerClass{ override fun toString(): String { return "innerclass" } fun test() = println("$name ${this@Student.toString()}") // name toString } }
密封類
使用 sealed 修飾符定義的類是密封類,密封類要求 所有直接子類必須定義在父類所在的文件中。
sealed 隱含的這個類是 open 類,不需要再顯式地聲明 open。
密封類的構(gòu)造方法是 private 。
java 中不能繼承密封類。kt 中可以在密封類所在的文件中定義密封類的子類,不是同一文件不繼承。
sealed 不能用于修飾接口。
// 密封類
sealed class Student{
// 內(nèi)部類可以繼承
private class S : Student()
}
// 同文件中可以繼承
class S1:Student()
數(shù)據(jù)類
使用
data修飾的類為數(shù)據(jù)類,數(shù)據(jù)類會根據(jù)主構(gòu)造函數(shù)自動重寫能用方法
通用方法一般包括有:equals(),toString(), hashCode()。
equals() 會檢測所有屬性的值是否相等
toString() 生成按聲明順序排列的所有字段的字符串表達形式
hashCode() 會返回一個根據(jù)所有屬性生成的哈希值。
equals ,toString() 和 hashCode() 只會考慮在主構(gòu)造函數(shù)中聲明的屬性。
-
由于 hashCode 會隨屬性值的變化而變化,因此在用于 map 中作為鍵值時,其 主構(gòu)造函數(shù)中的屬性必須要聲明為 val。
fun main(args:Array<String>){ val demo = Parent(1,"ls") println("hashCode = ${demo.hashCode()} toString = ${demo.toString()}") val d = Parent(1,"ls",20) println("hashCode = ${d.hashCode()} toString = ${d.toString()}") println(demo == demo) // true val map = hashMapOf<Parent,String>() val key = Parent(1,"ss") map.put(key,"000") println(map[key]) // 000 key.name = "xx" println(map[key]) // null } data class Parent(var age:Int,var name:String){ var score = 0 constructor(age:Int,name: String,score:Int):this(age,name){ this.score = score } }上述代碼中,如果修改其中一個屬性的值, map 返回的結(jié)果為 null。這是因為 hashCode 值已經(jīng)發(fā)生了變化。
-
data 類在生成時,會自動添加 copy 方法:在調(diào)用 copy() 時修改其些屬性的值。避免無法修改屬性值。這就是原型模式。
fun main(args:Array<String>){ val demo = Parent(1,"ls") val dc = demo.copy(2,"copy") // 兩者的 hashCode 返回結(jié)果不一樣 println("demo = ${demo.hashCode()} dc = ${dc.hashCode()}") }
類委托
在使用裝飾模式時經(jīng)常會出現(xiàn)這種情況:除了少部分要重寫的方法外,大部分方法都只是簡單的將請求轉(zhuǎn)發(fā)給被裝飾者。這些轉(zhuǎn)發(fā)請求的代碼就是所謂的樣板代碼 —— 所有的轉(zhuǎn)發(fā)代碼格式都一樣。
kt 中使用 by 關鍵字省略樣板代碼。
如下面代碼,除了 add 方法外,其余的方法都是直接將操作轉(zhuǎn)發(fā)給 innerSet 的同名操作。
// 使用 by 關鍵字常未重寫的操作委托給 innerSet ,而重寫的方法調(diào)用 Count 定義的
class Count<T>(private val innerSet:MutableCollection<T> = HashSet()):MutableCollection<T> by innerSet{
override fun add(element: T): Boolean {
println("add ${element}")
return innerSet.add(element)
}
}