Kotlin 接口與 Java 8 相似,使用 interface 關鍵字定義接口,容許方法有默認實現:swift
interface MyInterface { fun bar() // 未實現 fun foo() { //已實現 // 可選的方法體 println("foo") } }
一個類或者對象能夠實現一個或多個接口。後端
class Child : MyInterface { override fun bar() { // 方法體 } }
接口中的屬性只能是抽象的,不容許初始化值,接口不會保存屬性值,實現接口時,必須重寫屬性:ide
interface MyInterface{ var name:String //name 屬性, 抽象的 } class MyImpl:MyInterface{ override var name: String = "runoob" //重寫屬性 }
實現多個接口時,可能會遇到同一方法繼承多個實現的問題。例如:函數
interface A { fun foo() { print("A") } // 已實現 fun bar() // 未實現,沒有方法體,是抽象的 } interface B { fun foo() { print("B") } // 已實現 fun bar() { print("bar") } // 已實現 } class C : A { override fun bar() { print("bar") } // 重寫 } class D : A, B { override fun foo() { super<A>.foo() super<B>.foo() } override fun bar() { super<B>.bar() } } fun main(args: Array<String>) { val d = D() d.foo(); d.bar(); }
實例中接口 A 和 B 都定義了方法 foo() 和 bar(), 二者都實現了 foo(), B 實現了 bar()。由於 C 是一個實現了 A 的具體類,因此必需要重寫 bar() 並實現這個抽象方法。this
然而,若是咱們從 A 和 B 派生 D,咱們須要實現多個接口繼承的全部方法,並指明 D 應該如何實現它們。這一規則 既適用於繼承單個實現(bar())的方法也適用於繼承多個實現(foo())的方法。spa
Kotlin 能夠對一個類的屬性和方法進行擴展,且不須要繼承或使用 Decorator 模式。設計
擴展是一種靜態行爲,對被擴展的類代碼自己不會形成任何影響。code
擴展函數能夠在已有類中添加新的方法,不會對原類作修改,擴展函數定義形式:component
fun receiverType.functionName(params){ body }
如下實例擴展 User 類 :對象
class User(var name:String) /**擴展函數**/ fun User.Print(){ print("用戶名 $name") } fun main(arg:Array<String>){ var user = User("Runoob") user.Print() }
下面代碼爲 MutableList 添加一個swap 函數:
// 擴展函數 swap,調換不一樣位置的值 fun MutableList<Int>.swap(index1: Int, index2: Int) { val tmp = this[index1] // this 對應該列表 this[index1] = this[index2] this[index2] = tmp } fun main(args: Array<String>) { val l = mutableListOf(1, 2, 3) // 位置 0 和 2 的值作了互換 l.swap(0, 2) // 'swap()' 函數內的 'this' 將指向 'l' 的值 println(l.toString()) }
this關鍵字指代接收者對象(receiver object)(也就是調用擴展函數時, 在點號以前指定的對象實例)。
擴展函數是靜態解析的,並非接收者類型的虛擬成員,在調用擴展函數時,具體被調用的的是哪個函數,由調用函數的的對象表達式來決定的,而不是動態的類型決定的:
open class C class D: C() fun C.foo() = "c" // 擴展函數 foo fun D.foo() = "d" // 擴展函數 foo fun printFoo(c: C) { println(c.foo()) // 類型是 C 類 } fun main(arg:Array<String>){ printFoo(D()) //c }
若擴展函數和成員函數一致,則使用該函數時,會優先使用成員函數。
class C { fun foo() { println("成員函數") } } fun C.foo() { println("擴展函數") } fun main(arg:Array<String>){ var c = C() c.foo() //成員函數 }
在擴展函數內, 能夠經過 this 來判斷接收者是否爲 NULL,這樣,即便接收者爲 NULL,也能夠調用擴展函數。例如:
fun Any?.toString(): String { if (this == null) return "null" // 空檢測以後,「this」會自動轉換爲非空類型,因此下面的 toString() // 解析爲 Any 類的成員函數 return toString() } fun main(arg:Array<String>){ var t = null println(t.toString()) }
>擴展屬性
除了函數,Kotlin 也支持屬性對屬性進行擴展:
val <T> List<T>.lastIndex: Int get() = size - 1
擴展屬性容許定義在類或者kotlin文件中,不容許定義在函數中。初始化屬性由於屬性沒有後端字段(backing field),因此不容許被初始化,只能由顯式提供的 getter/setter 定義。
val Foo.bar = 1 // 錯誤:擴展屬性不能有初始化器
擴展屬性只能被聲明爲 val。
若是一個類定義有一個伴生對象 ,你也能夠爲伴生對象定義擴展函數和屬性。
伴生對象經過"類名."形式調用伴生對象,伴生對象聲明的擴展函數,經過用類名限定符來調用:
class MyClass { companion object { } // 將被稱爲 "Companion" } fun MyClass.Companion.foo() { println("伴隨對象的擴展函數") } val MyClass.Companion.no: Int get() = 10 fun main(args: Array<String>) { println("no:${MyClass.no}") MyClass.foo() }
一般擴展函數或屬性定義在頂級包下:
package foo.bar fun Baz.goo() { …… }
要使用所定義包以外的一個擴展, 經過import導入擴展的函數名進行使用:
package com.example.usage import foo.bar.goo // 導入全部名爲 goo 的擴展 // 或者 import foo.bar.* // 從 foo.bar 導入一切 fun usage(baz: Baz) { baz.goo() }
在一個類內部你能夠爲另外一個類聲明擴展。
在這個擴展中,有個多個隱含的接受者,其中擴展方法定義所在類的實例稱爲分發接受者,而擴展方法的目標類型的實例稱爲擴展接受者。
class D { fun bar() { println("D bar") } } class C { fun baz() { println("C baz") } fun D.foo() { bar() // 調用 D.bar baz() // 調用 C.baz } fun caller(d: D) { d.foo() // 調用擴展函數 } } fun main(args: Array<String>) { val c: C = C() val d: D = D() c.caller(d) }
在 C 類內,建立了 D 類的擴展。此時,C 被成爲分發接受者,而 D 爲擴展接受者。從上例中,能夠清楚的看到,在擴展函數中,能夠調用派發接收者的成員函數。
假如在調用某一個函數,而該函數在分發接受者和擴展接受者均存在,則以擴展接收者優先,要引用分發接收者的成員你可使用限定的 this 語法。
以成員的形式定義的擴展函數, 能夠聲明爲 open , 並且能夠在子類中覆蓋. 也就是說, 在這類擴展函數的派 發過程當中, 針對分發接受者是虛擬的(virtual), 但針對擴展接受者仍然是靜態的。
open class D { } class D1 : D() { } open class C { open fun D.foo() { println("D.foo in C") } open fun D1.foo() { println("D1.foo in C") } fun caller(d: D) { d.foo() // 調用擴展函數 } } class C1 : C() { override fun D.foo() { println("D.foo in C1") } override fun D1.foo() { println("D1.foo in C1") } } fun main(args: Array<String>) { C().caller(D()) // 輸出 "D.foo in C" C1().caller(D()) // 輸出 "D.foo in C1" —— 分發接收者虛擬解析 C().caller(D1()) // 輸出 "D.foo in C" —— 擴展接收者靜態解析 }
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) }
組件函數容許數據類在解構聲明中使用:
val jane = User("Jane", 35) val (name, age) = jane println("$name, $age years of age") // prints "Jane, 35 years of age"
標準庫提供了 Pair 和 Triple 。在大多數情形中,命名數據類是更好的設計選擇,由於這樣代碼可讀性更強並且提供了有意義的名字和屬性。
密封類用來表示受限的類繼承結構:當一個值爲有限幾種的類型, 而不能有任何其餘類型時。在某種意義上,他們是枚舉類的擴展:枚舉類型的值集合 也是受限的,但每一個枚舉常量只存在一個實例,而密封類 的一個子類能夠有可包含狀態的多個實例。
聲明一個密封類,使用 sealed 修飾類,密封類能夠有子類,可是全部的子類都必需要內嵌在密封類中。
sealed 不能修飾 interface ,abstract class(會報 warning,可是不會出現編譯錯誤)
sealed class Expr data class Const(val number: Double) : Expr() data class Sum(val e1: Expr, val e2: Expr) : Expr() object NotANumber : Expr() fun eval(expr: Expr): Double = when (expr) { is Const -> expr.number is Sum -> eval(expr.e1) + eval(expr.e2) NotANumber -> Double.NaN
// 再也不須要 `else` 子句,由於咱們已經覆蓋了全部的狀況
}
使用密封類的關鍵好處在於使用 when 表達式 的時候,若是可以 驗證語句覆蓋了全部狀況,就不須要爲該語句再添加一個 else 子句了。