Kotlin(六)、類.對象和接口

一、定義類

接口

//使用interface關鍵字聲明一個Kotlin接口
interface Clickable {
    fun click()
}

//實現這個接口
class Button : Clickable {//使用`:`代替Java中的extends和implements關鍵字,和Java一樣單繼承多實現
    override fun click() = println("I was clicked")//override修飾符和Java中@Override註解一樣,不同的是Kotlin強制要求
}
 
//使用
Button.click()

接口方法可以有一個默認實現。Java8中需要在這樣的實現上標註default關鍵字,Kotlin中只需要提供一個方法體

//接口中定義一個帶方法體的方法
interface Clickable {
    fun click()
    fun showOff() = println("I'm clickable")
}

若實現了這個接口則需要爲click提供一個實現,可以爲showOff重新定義行爲,如果默認行爲可以滿足需求也可以省略

//定義另一個實現同樣方法的接口
interface Focusable{
    fun showOff()= println("I'm focusable")
}

在一個類中同時實現兩個接口,每個接口都具有包含默認實現的showOff方法,這時候就算有默認實現也不行,必須顯示的實現showOff

class Button :Focusable,Clickable{
    override fun showOff() {
        //必須提供顯示實現,使用<父類名>標明想要調用的父類方法
        super<Focusable>.showOff()
        super<Clickable>.showOff()
        //當然也可以只super其中一個
    }

    override fun click() {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }

}

open、final、abstract

Kotlin中類和方法默認final,好處就是擁有了智能轉換功能。智能轉換只能在進行類型檢查後沒有改變過的變量上起作用。對於一個類意味着只能是val類型切沒有自定義getter的屬性上使用,所以前提就是final。由於屬性默認final,所以可以不加思考的使用智能轉換。

open class Button : Clickable {//open的,其他類可以繼承

    fun disable() {}//final的,不能在子類重寫

    open fun animate() {}//open的,可以子類重寫

    override fun click() {}//重寫了子類的open,所以默認open

    final override fun showOff() {}//重寫了其父類的open方法,但其子類不能重寫

}

Kotlin中通Java一樣可以爲類聲明abstract的,不能被實例化。一個抽象類通常包含一些沒有實現並且必須在子類重寫的抽象成員。抽象類始終是open的,所以不需要顯示的使用open修飾符

abstract class Animated {//抽象類,不能創建實例
    abstract fun animate()//抽象函數,他沒有實現必須被子類重寫
    open fun stopAnimating() {}//抽象類的非抽象函數不是默認open,可以主表2000
    fun animateTwice() {}
}
修飾符 相關成員 評註
final 不能被重寫 類中成員默認使用
open 可以被重寫 需要明確的表明
abstract 必須被重寫 只能在抽象類中使用;抽象成員不能有實現
override 重寫父類或接口中的成員 如果沒有使用final表明,重寫的成員默認是開放的

可見性修飾符

修飾符 類成員 頂層聲明
public 所有地方可見 所有地方可見
internal 模塊中可見 模塊中可見
protected 子類中可見 ——
private 類中可見 文件中可見

內部類和嵌套類

類A在另一個類B中聲明 在Java中 在Kotlin中
嵌套類 static class A class A
內部類 class A inner class

密封類


open class Expr
class Num(val value: Int) : Expr()
class Sunm(val left: Expr, val right: Expr) : Expr()
fun eval(e:Expr):Int=
        when(e){
            is Num->e.value
            is Sunm-> eval(e.right)+ eval(e.left)
            else->
                throw IllegalArgumentException("Unknown expression")
        }

當使用when結構來執行表達式的時候,Kotlin編譯器會強制檢查默認選項。在這個例子中,不能返回一個有意義的值,所以直接拋出異常。
總是不得不添加一個默認分支很不方便。更重要的是,如果你有新添加了一個新的子類,編譯器並不能發現有新的子類,這是就會走到默認分支,有可能導致潛在的bug。
Kotlin爲這個問題提供了一個解決方案:sealed類。爲父類添加一個sealed修飾符,對可能創建的子類做出嚴格的限制。所有的直接子類必須嵌套在父類中。

sealed class Expr {
    class Num(val value: Int) : Expr()
    class Sunm(val left: Expr, val right: Expr) : Expr()
}


fun eval(e: Expr): Int =
        when (e) {
            is Expr.Num -> e.value
            is Expr.Sunm -> eval(e.right) + eval(e.left)
        }

二、定義構造方法

寫一個簡單的擁有構造函數的class

//一個主構造,沒有從構造
class User constructor(nickname: String) {//帶一個參數的主構造方法
    val nickname: String

    init {//初始化語句塊
        this.nickname = nickname
    }
}

因爲主構造沒有註解和可見性修飾,並且初始化可以與屬性結合

class User (nickname: String) {
    val nickname = nickname
}

由於是val修飾的變量,所以還可以簡化爲

class User (val nickname: String)

那麼這就是最簡單的語法了

若不想被實例化,則需要構造函數私有化

class User private constructor(val nickname: String)

接口中聲明屬性

Kotlin中接口是可以包含屬性聲明的

interface User {
    val nickname: String
}

也就是說實現User接口的類需要提供一個取得nickname的方式

//PrivateUser使用了簡潔的語法直接在主構造中聲明瞭一個屬性,這個屬性來自User所以標記爲override
class PrivateUser(override val nickname: String) : User

//SubscribingUser的nickname屬性通過一個自定義getter實現
class SubscribingUser(private val email: String) : User {
    override val nickname: String
        get() = email.substringBefore('@')
}

//FacebookUser在初始化時將nickname屬性與值關聯。這個函數開銷很大,每次都需要調用getFacebookName初始化很多次
class FacebookUser(val accountId: Int) : User {
    override val nickname = getFacebookName(accountId)
}

二、數據類和類委託

數據類

使用data修飾符得到一個重寫了所有標準Java方法的類:

data class Client(val name: String, val postalCode: Int)
  • equals用來比較實例
  • hashCode用來作爲如Hashmap這種基於哈希容器的鍵
  • toString用來爲類生成按聲明順序排列的所有字段的字符串表達形式
    equals和hashCode方法會將所有在主構造方法中聲明的屬性納入考慮。生成的equals方法會檢測所有的屬性的值是否相等。hashCode方法會返回一個根據所有屬性生成的哈希值。注意沒有在主構造中聲明的屬性將不會加入到相等性檢查和哈希值計算中去。

這只是爲data類生成的一部分方法,下一篇會介紹更多。

原則上是可以使用var可變類型的,但是還是更鼓勵使用val不可變數據類型,好處很多,特別是在多線程代碼中:一旦一個對象被創建出來了,他會一直保持初始狀態,不用擔心代碼工作時被其他線程修改。

爲了解決val的操作數據問題,Kotlin編譯器多生成了一個方法:允許copy類實例,並在copy的同時修改屬性值。副本有單獨的生命週期與原實例互不影響

val bob = Client("Bob", 1234)
println(bob.copy(postalCode = 4321))

委託類

重寫部分功能,其他通用功能委託給被包裝類

class CountingSet<T>(val innerSet: MutableCollection<T> = HashSet<T>())
    : MutableCollection<T> by innerSet {
    override fun add(element: T): Boolean {
        //重寫
        return innerSet.add(element)
    }
    //剩下的接口委託給被包裝容器,字段爲innerSet
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章