類、物件和介面
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的匿名物件可以實現多個介面或者不實現介面