Kotlin 四 接口

一 接口

 

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
}
  • receiverType:表示函數的接收者,也就是函數擴展的對象
  • functionName:擴展函數的名稱
  • params:擴展函數的參數,能夠爲NULL

如下實例擴展 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 ;

  • 數據類不能夠聲明爲 abstractopensealed 或者 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 子句了。 

相關文章
相關標籤/搜索