1、定義類繼承結構
1.1 kotlin中的接口
聲明接口
interface Clickable{
fun click()
}
聲明了一個只有一個抽象方法的接口,和java中一樣,實現這個接口的類要提供這個抽象方法的具體實現。
實現接口
class Button:Clickable{
override fun click()=println("I was clicked")//override修飾符是強制要求的
}
在kotlin中“:”相當於java中的extends或implements。
在接口中定義一個帶方法體的方法
interface Clickable{
fun click()
fun showOff()=println("I'm clickable!")//帶默認實現的方法
}
我們實現這個接口的類中要為click()方法提供實現。而對於showOff()方法則有些無所謂了,你可以直接調用,也可以重新定義。
1.2 open、final和abstract修飾符:默認為final
kotlin中類和方法默認都是final的,如果想允許一個類有子類,那么需要用open修飾。
open class RichButton:Clickable{//這個類是open的:其他類可以繼承它
fun disable(){}//這個函數是默認是final修飾的,雖然沒有寫出來。不能再子類中重寫它
open fun animate(){}//用open修飾,可以再子類中重寫
override fun click(){}//重寫了一個函數
}
上面的這個click(){}函數是重寫的,它默認是open,可以被RichButton的子類再次重寫,如果我們不想讓它重寫了,那么可以用final修飾它。
open class RichButton:Clickable{
final override fun click(){}//這樣子類就不能重寫了
}
抽象類
kotlin中同樣有抽象類,也是用abstract關鍵字,這種類不能被實例化。抽象類的成員始終是open的,我們不需要明確用open修飾。
abstract class Animated{
abstract fun animate()//抽象函數,子類必須重寫它
open fun stopAnimating(){}
fun animateTwice(){}//open修飾符可寫可不寫
}
| 修飾符 | 相關成員 | 備注 |
|---|---|---|
| final | 不能被重寫 | 在類中被默認使用 |
| open | 可以被重寫 | 必須要標明 |
| abstract | 必須被重寫 | 只能在抽象類中使用,抽象成員不能有實現 |
| override | 重寫父類或接口中的成員 | 如果沒有使用final標明,重寫的成員默認是open的 |
1.3 可見性修飾符:默認為public
| 修飾符 | 類成員 | 頂層聲明 |
|---|---|---|
| public | 所有地方可見 | 所有地方可見 |
| internal | 模塊中可見 | 模塊中可見 |
| protected | 子類中可見 | - |
| private | 類中可見 | 文件中可見 |
- kotlin中protected成員只在類和它的子類中可見,不可以從同一包內訪問一個protected成員。
- 外部類不能看到內部類中private中的成員。
1.4 內部類和嵌套類:默認是嵌套類
| 類A在另一個類B中聲明 | 在java中 | 在kotlin中 |
|---|---|---|
| 嵌套類(不存儲外部類的引用) | static class A | class A |
| 內部類(存儲外部類的引用) | class A | inner class A |
1.5 密封類:定義受限的類繼承結構
密封類用來表示受限的類繼承結構:當一個值為有限幾種的類型, 而不能有任何其他類型時。在某種意義上,他們是枚舉
類的擴展:枚舉類型的值集合 也是受限的,但每個枚舉常量只存在一個實例,而密封類 的一個子類可以有可包含狀態的多個實例。
聲明一個密封類,使用 sealed 修飾類,密封類可以有子類,但是所有的子類都必須要內嵌在密封類中。
使用密封類的關鍵好處在於使用 when 表達式 的時候,如果能夠 驗證語句覆蓋了所有情況,就不需要為該語句再添加一個 else 子句了。
fun eval(expr: Expr): Double = when(expr) {
is Expr.Const -> expr.number
is Expr.Sum -> eval(expr.e1) + eval(expr.e2)
Expr.NotANumber -> Double.NaN
// 不再需要 `else` 子句,因為我們已經覆蓋了所有的情況
}
2、聲明一個帶非默認構造方法或屬性的類
2.1 初始化類:主構造方法和初始化語句塊
我們先聲明一個簡單類
class User(val nickname:String)
括號里面的語句塊叫作主構造方法。
它又兩個目的:①標明構造方法的參數②定義使用這些參數初始化的屬性
它相當於
class User constructor(nickname:String){//帶一個參數的主構造方法
val nickname:String
init{//初始化語句塊
this.nickname=nickname
}
}
如果沒有給類聲明任何構造方法,將會生成一個不帶參數的默認構造方法。
2.2 構造方法:用不同的方式來初始化父類
kotlin中的重載
open class View{
constructor(ctx:Context){
}
constructor(ctx:Context,attr:AttributeSet){
}
}
這個類沒有聲明一個主構造方法,它聲明的是兩個從構造方法。
2.3 實現在接口中聲明的屬性
java中不可以聲明抽象的成員變量,在kotlin中可以。
interface User{
val nickname:String
}
class PrivateUser(override val nickname:String):User
除了抽象屬性聲明外,接口還可以包含具有getter和setter屬性
2.4 通過getter或setter訪問支持字段
實現一個既可以存儲值又可以在值被訪問和修改時提供額外邏輯的屬性。
class User(val name:String){
var address:String="unspecified"
set(value:String){
println("""
Address was changed for $name:
"$field"->"$value".""".trimIndent())//讀取支持字段的值
field=value//更新支持字段的值
}
}
2.5 修改訪問器的可見性
聲明一個具有private setter的屬性
class LengthCounter{
val counter:Int=0
private set
fun addWord(word:String){
counter+=word.length
}
}
3、編譯器生成的方法:數據類和類委托
3.1 通用對象方法
toString()
equals()
hashCode()
3.2 數據類
Kotlin 可以創建一個只包含數據的類,關鍵字為 data:
data class User(val name: String, val age: Int)
編譯器會自動的從主構造函數中根據所有聲明的屬性提取以下函數:
- equals() / hashCode()
- toString() 格式如 "User(name=John, age=42)"
- componentN() functions 對應於屬性,按聲明順序排列
- copy() 函數
如果這些函數在類中已經被明確定義了,或者從超類中繼承而來,就不再會生成。
為了保證生成代碼的一致性以及有意義,數據類需要滿足以下條件:
- 主構造函數至少包含一個參數。
- 所有的主構造函數的參數必須標識為val 或者 var ;
- 數據類不可以聲明為 abstract, open, sealed 或者 inner;
- 數據類不能繼承其他類 (但是可以實現接口)。
復制使用 copy() 函數,我們可以使用該函數復制對象並修改部分屬性, 對於上文的 User 類,其實現會類似下面這樣:
fun copy(name: String = this.name, age: Int = this.age) = User(name, age)
使用 copy 類復制 User 數據類,並修改 age 屬性:
data class User(val name: String, val age: Int)
fun main(args: Array<String>) {
val jack = User(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)
println(jack)
println(olderJack)
}
3.3 類委托:使用"by"關鍵字
委托模式是軟件設計模式中的一項基本技巧。在委托模式中,有兩個對象參與處理同一個請求,接受請求的對象將請求委托給另一個對象來處理。
類的委托即一個類中定義的方法實際是調用另一個類的對象的方法來實現的。
以下實例中派生類 Derived 繼承了接口 Base 所有方法,並且委托一個傳入的 Base 類的對象來執行這些方法。
// 創建接口
interface Base {
fun print()
}
// 實現此接口的被委托的類
class BaseImpl(val x: Int) : Base {
override fun print() { print(x) }
}
// 通過關鍵字 by 建立委托類
class Derived(b: Base) : Base by b
fun main(args: Array<String>) {
val b = BaseImpl(10)
Derived(b).print() // 輸出 10
}
在 Derived 聲明中,by 子句表示,將 b 保存在 Derived 的對象實例內部,而且編譯器將會生成繼承自 Base 接口的所有方法, 並將調用轉發給 b
Kotlin 直接支持委托模式,更加優雅,簡潔。Kotlin 通過關鍵字 by 實現委托
4、"object"關鍵字:將聲明一個類與創建一個實例結合起來
4.1 對象聲明
kotlin中使用object關鍵字來聲明一個對象
通過對象聲明獲取一個單例很方便
object DataProviderManager {
fun registerDataProvider(provider: DataProvider) {
// ……
}
val allDataProviders: Collection<DataProvider>
get() = // ……
}
當對象聲明在另一個類的內部時,這個對象並不能通過外部類的實例訪問到該對象,而只能通過類名來訪問,同樣該對象也不能直接訪問到外部類的方法和變量。
4.2 伴生對象
類內部的對象聲明可以用 companion 關鍵字標記,這樣它就與外部類關聯在一起,我們就可以直接通過外部類訪問到對象的內部元素。
class MyClass {
companion object Factory {
fun create(): MyClass = MyClass()
}
}
val instance = MyClass.create() // 訪問到對象的內部元素
我們可以省略掉該對象的對象名,然后使用 Companion 替代需要聲明的對象名。
一個類里面只能聲明一個內部關聯對象,即關鍵字 companion 只能使用一次。
4.3 對象表達式
通過對象表達式實現一個匿名內部類的對象用於方法的參數中:
window.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
// ...
}
override fun mouseEntered(e: MouseEvent) {
// ...
}
})
與java匿名內部類只能擴展一個類或實現一個接口不同,kotlin的匿名對象可以實現多個接口或者不實現接口。
