Kotlin 類、對(duì)象尔邓、接口摘要

接口

接口的方法可以有一個(gè)默認(rèn)實(shí)現(xiàn)

interface Clickable {
    fun click() // 普通的方法聲明
    fun showOff() = println("I'm clickable!") // 帶默認(rèn)實(shí)現(xiàn)的方法
}

如果你實(shí)現(xiàn)了這個(gè)接口晾剖,并且對(duì)默認(rèn)行為感到滿意的話可以省略 showOff的實(shí)現(xiàn),但你需要為 click 提供一個(gè)實(shí)現(xiàn)梯嗽。

如果另外一個(gè)借口也有一個(gè)同樣的方法showOff

interface Focusable {
    fun setFocus(b: Boolean) = println("I ${if (b) "got" else "lost"} focus.")
    fun showOff() = println("I'm focusable!")
}

如果你需要在你的類中實(shí)現(xiàn)這兩個(gè)接口會(huì)發(fā)生什么齿尽?

如果你沒有顯式實(shí)現(xiàn) showOff,你會(huì)得到如下的編譯錯(cuò)誤:

The class 'Button' must override public open fun showOff() because it inherits many implementations of it.

Kotlin 編譯器強(qiáng)制要求你提供你自己的實(shí)現(xiàn)灯节。

現(xiàn)在 Button 類實(shí)現(xiàn)了兩個(gè)接口循头。你通過調(diào)用繼承的兩個(gè)父類型中的實(shí)現(xiàn)來實(shí)現(xiàn)自己的 showOff()

class Button : Clickable, Focusable {
    override fun click() = println("I was clicked")

    override fun showOff() { // 如果同樣的繼承成員有不止一個(gè)實(shí)現(xiàn)绵估,你必須提供一個(gè)顯式實(shí)現(xiàn)。
super<Clickable>.showOff()
super<Focusable>.showOff() // 使用尖括號(hào)加上父類型名字的 "super" 表明了你想要調(diào)用哪一個(gè)父類的方法
    }
}

Java 中你可以把基類的名字放在 super 關(guān)鍵字的前面贷岸,就像 Clickable.super.showOff() 這樣壹士,在 Kotlin 中需要把基類的名字放在尖括號(hào)中:super<Clickable>.showOff()

繼承

如果你想允許創(chuàng)建一個(gè)類的子類,你需要使用 open 修飾符來標(biāo)示這個(gè)類偿警。此外躏救,你需要給每一個(gè)可以被重寫的屬性或方法添加 open 修飾符。

open class RichButton : Clickable { // 這個(gè)類是open的:其他類可以繼承它螟蒸。
    fun disable() {} // 這個(gè)函數(shù)是final的:你不能在子類中重寫它盒使。
    open fun animate() {} // 這個(gè)函數(shù)是open的:你可以在子類中重寫它。
    override fun click() {} // 這個(gè)函數(shù)重寫了一個(gè)開放函數(shù)并且它本身同樣是open的七嫌。
}

注意少办,如果你重寫了一個(gè)基類或者接口的成員,重寫了的成員同樣默認(rèn)是 open诵原。如果你想改變這一行為英妓,阻止你的類的子類重寫你的實(shí)現(xiàn),你可以顯式將重寫的成員標(biāo)注為 final绍赛。

open class RichButton : Clickable {
    final override fun click() {}
}

在 Kotlin 中蔓纠,同 Java 一樣,你可以將一個(gè)類聲明為 abstract 吗蚌,抽象類始終是開放的腿倚,所以你不需要顯式使用 open 修飾符。

abstract class Animated { // 這個(gè)類是抽象的:你不能創(chuàng)建它的實(shí)例蚯妇。
    abstract fun animate() // 這個(gè)函數(shù)是抽象的:它沒有實(shí)現(xiàn)必須被子類重寫敷燎。
    open fun stopAnimating() {} // 抽象類中的非抽象函數(shù)并不是默認(rèn)開放的,但是可以標(biāo)注為開放的。
    fun animateTwice() {}
}
可見性

Kotlin 中的可見性修飾符與 Java 中的類似。你同樣可以使用 public, protectedprivate 修飾符蒲犬。

  • 但是默認(rèn)的可見性是不一樣的:如果你省略了修飾符,聲明就是 public 的澄成。

  • Java 中的默認(rèn)可見性——包私有,在 Kotlin 中并沒有使用畏吓。

  • 在 Java 中,你可以從同一個(gè)包中訪問一個(gè) protected 的成員卫漫,但 Kotlin 中protected 成員只在類和它的子類中可見菲饼。

  • 要注意的是類的擴(kuò)展函數(shù)不能訪問它的 privateprotected 成員。

嵌套類

  • 默認(rèn)情況下Kotlin 的嵌套類不能訪問外部類的實(shí)例列赎,而 Java 中可以

  • Kotlin 中沒有顯式修飾符的嵌套類與 Java 中的 static 嵌套類是一樣的

  • 要把它變成一個(gè)內(nèi)部類來持有一個(gè)外部類的引用的話你需要使用 inner 修飾符

    類 A 在另一個(gè)類 B 中聲明 在 Java 中 在 Kotlin 中
    嵌套類(不存儲(chǔ)外部類的引用) static class A class A
    內(nèi)部類(存儲(chǔ)外部類的引用) class A inner class A

在 Kotlin 中引用外部類實(shí)例的語法也與 Java 不同宏悦。你需要使用 this@OuterInner 類去訪問 Outer 類:

class Outer {
    inner class Inner {
        fun getOuterReference(): Outer = this@Outer
    }
}

密封類(sealed)

父類添加一個(gè) sealed 修飾符,對(duì)可能創(chuàng)建的子類做出嚴(yán)格的限制。所有的直接子類必須嵌套在父類中饼煞。

sealed class Expr { // 將基類標(biāo)記為密封的
    class Num(val value: Int) : Expr() // 將所有可能的子類作為密封類列出
    class Sum(val left: Expr, val right: Expr) : Expr()
}

fun eval(e: Expr): Int =
    when (e) { // "when" 表達(dá)式涵蓋了所有可能的情況源葫,所以不再需要 "else" 分支
        is Expr.Num -> e.value
        is Expr.Sum -> eval(e.right) + eval(e.left)
    }
  • 如果你在 when 表達(dá)式里面處理所有 sealed 類的子類,你就不再需要提供默認(rèn)分支砖瞧。注意 sealed 修飾符隱含了這個(gè)類是一個(gè)開放類息堂;你不再需要顯式添加 open 修飾符。

  • 當(dāng)你在 when 中使用 sealed類并且添加一個(gè)新的子類的時(shí)候块促,有返回值的 when 表達(dá)式會(huì)導(dǎo)致編譯失敗荣堰,它會(huì)告訴你哪里的代碼必須要修改。

  • Expr 類有一個(gè)只能在類內(nèi)部調(diào)用的 private 構(gòu)造方法竭翠。你也不能聲明一個(gè) sealed接口振坚。為什么?如果你能這樣做的話斋扰,Kotlin編譯器不能保證任何人都不能在 Java 代碼中實(shí)現(xiàn)這個(gè)接口渡八。

構(gòu)造函數(shù)

主構(gòu)造函數(shù)和初始化語句塊

class User constructor(_nickname: String) { // 帶一個(gè)參數(shù)的主構(gòu)造方法
    val nickname: String

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

簡(jiǎn)化后

class User(val nickname: String) // "val" 意味著相應(yīng)的屬性會(huì)用構(gòu)造方法的參數(shù)來初始化

如果你的類具有一個(gè)父類,主構(gòu)造方法同樣需要初始化父類传货。你可以通過在基類列表的父類引用中提供父類構(gòu)造方法參數(shù)的方式來做到這一點(diǎn):

open class User(val nickname: String) { ... }
class TwitterUser(nickname: String) : User(nickname) { ... }

如果你沒有給一個(gè)類聲明任何的構(gòu)造方法屎鳍,將會(huì)生成一個(gè)不做任何事情的默認(rèn)構(gòu)造方法:

open class Button // 將會(huì)生成一個(gè)不帶任何參數(shù)的默認(rèn)構(gòu)造方法

如果繼承了 Button 類并且沒有提供任何的構(gòu)造方法,你必須顯式調(diào)用父類的構(gòu)造方法即使它沒有任何的參數(shù):

class RadioButton: Button()

這就是為什么在父類名稱后面還需要一個(gè)空的括號(hào)

注意與接口的區(qū)別:接口沒有構(gòu)造方法损离,所以在你實(shí)現(xiàn)一個(gè)接口的時(shí)候哥艇,你不需要在父類型列表中它名稱后面再加上括號(hào)。

數(shù)據(jù)類

如果你為你的類添加 data 修飾符僻澎,必要的方法將會(huì)自動(dòng)生成toString貌踏,equalshashCode

data class Client(val name: String, val postalCode: Int)

請(qǐng)注意雖然數(shù)據(jù)類的屬性并沒有必須是 val —— 你同樣可以使用 var —— 但還是強(qiáng)烈推薦只使用只讀屬性窟勃,讓數(shù)據(jù)類的實(shí)例不可變祖乳。

為了讓使用不可變對(duì)象的數(shù)據(jù)類變得更容易,Kotlin 編譯器為他們多生成了一個(gè)方法copy():一個(gè)允許拷貝類的實(shí)例的方法秉氧,并在拷貝的同時(shí)修改某些屬性的值眷昆。

類委托:"by" 關(guān)鍵字

下面這段代碼,為了實(shí)現(xiàn) Collection借口汁咏,你必須實(shí)現(xiàn)所有的方法

class DelegatingCollection<T> : Collection<T> {
    private val innerList = arrayListOf<T>()
    override val size: Int get() = innerList.size
    override fun isEmpty(): Boolean = innerList.isEmpty()
    override fun contains(element: T): Boolean = innerList.contains(element)
    override fun iterator(): Iterator<T> = innerList.iterator()
    override fun containsAll(elements: Collection<T>): Boolean =
       innerList.containsAll(elements)
}

而如果使用類委托亚斋,就可以變成

class DelegatingCollection<T>(
        innerList: Collection<T> = ArrayList<T>()
) : Collection<T> by innerList {}

類中所有的方法實(shí)現(xiàn)都消失了,DelegatingCollection默認(rèn)會(huì)使用ArrayList的行為攘滩,如果你需要修改某一個(gè)函數(shù)的行為帅刊,只需要重寫這一個(gè)函數(shù)即可。

"object"關(guān)鍵字

Tips: 這是我在學(xué)習(xí) Koltin 是感受到最需要注意的地方漂问,objectKotlin中的用法和重要赖瞒。

Kotlin 中 object 關(guān)鍵字在多種情況下出現(xiàn)女揭,但是它們都遵循同樣的核心理念:這個(gè)關(guān)鍵字定義一個(gè)類并同時(shí)創(chuàng)建一個(gè)實(shí)例(換句話說就是一個(gè)對(duì)象)。讓我們來看看使用它的不同場(chǎng)景:

  • 對(duì)象聲明是定義單例的一種方式栏饮。
  • 伴生對(duì)象可以持有工廠方法和其他與這個(gè)類相關(guān)吧兔,但在調(diào)用時(shí)并不依賴類實(shí)例的方法。它們的成員可以通過類名來訪問袍嬉。
  • 對(duì)象表達(dá)式用來替代 Java 的匿名內(nèi)部類境蔼。
對(duì)象聲明

對(duì)象聲明將 類聲明 與該類的 單一實(shí)例 聲明結(jié)合到了一起。

object Payroll {
    val allEmployees = arrayListOf<Person>()
    fun calculateSalary() {
        for (person in allEmployees) {
            ...
        }
    }
}

與變量一樣冬竟,對(duì)象聲明允許你使用對(duì)象名加 . 字符的方式來調(diào)用方法和訪問屬性:

Payroll.allEmployees.add(Person(...))
Payroll.calculateSalary()

你同樣可以在類中聲明對(duì)象欧穴。這樣的對(duì)象同樣只有一個(gè)單一實(shí)例,它們?cè)诿總€(gè)容器類的實(shí)例中并不具有不同的實(shí)例

data class Person(val name: String) {
    object NameComparator : Comparator<Person> {
        override fun compare(p1: Person, p2: Person): Int =
            p1.name.compareTo(p2.name)
    }
}

在 Java 中使用 Kotlin 對(duì)象

Kotlin 中的對(duì)象聲明被編譯成了通過靜態(tài)字段來持有它的單一實(shí)例的類泵殴,這個(gè)字段名字始終都是 INSTANCE涮帘。如果你在 Java 中實(shí)現(xiàn)單例模式,你也許也會(huì)順手做同樣地事笑诅。因此调缨,要從 Java 代碼使用 Kotlin 對(duì)象,可以通過訪問靜態(tài)的 INSTANCE 字段:

/* Java */
CaseInsensitiveFileComparator.INSTANCE.compare(file1, file2);

在這個(gè)例子中吆你,INSTANCE 字段的類型是 CaseInsensitiveFileComparator弦叶。

伴生對(duì)象companion

Kotlin 中的類不能擁有靜態(tài)成員;Java 的 static 關(guān)鍵字并不是 Kotlin 語言的一部分妇多。使用伴生對(duì)象伤哺,會(huì)讓我們的方法調(diào)用看起來很像static方法。

class A {
    companion object {
        fun bar() {
            println("Companion object called")
        }
    }
}
>>> A.bar()
Companion object called

重要用途者祖,實(shí)現(xiàn)工廠方法模式

class User private constructor(val nickname: String) { // 將主構(gòu)造方法標(biāo)記為私有
    companion object { // 聲明伴生對(duì)象
        fun newSubscribingUser(email: String) = // 聲明一個(gè)命名的伴生對(duì)象
            User(email.substringBefore('@'))

        fun newFacebookUser(accountId: Int) = // 用工廠方法通過 Facebook 賬號(hào)來創(chuàng)建一個(gè)新用戶
            User(getFacebookName(accountId))
    }
}

你可以通過類名來調(diào)用 companion object 的方法:

>>> val subscribingUser = User.newSubscribingUser("bob@gmail.com")
>>> val facebookUser = User.newFacebookUser(4)

>>> println(subscribingUser.nickname)
bob
作為普通對(duì)象使用的伴生對(duì)象
class Person(val name: String) {
    companion object Loader {
        fun fromJSON(jsonText: String): Person = ...
    }
}
>>> person = Person.Loader.fromJSON("{name: 'Dmitry'}") // 你可以通過兩種方式來調(diào)用 fromJSON
>>> person.name
Dmitry
>>> person2 = Person.fromJSON("{name: 'Brent'}") // 你可以通過兩種方式來調(diào)用 fromJSON
>>> person2.name
Brent

如果你省略了伴生對(duì)象的名字立莉,默認(rèn)的名字將會(huì)分配為 Companion

在伴生對(duì)象中實(shí)現(xiàn)接口

interface JSONFactory<T> {
    fun fromJSON(jsonText: String): T
}

class Person(val name: String) {
    companion object : JSONFactory<Person> {
       override fun fromJSON(jsonText: String): Person = ... // 實(shí)現(xiàn)接口的伴生對(duì)象
    }
}

這時(shí)七问,如果你有一個(gè)函數(shù)使用抽象方法來加載實(shí)體蜓耻,你可以將 Person 對(duì)象傳進(jìn)去。

fun loadFromJSON<T>(factory: JSONFactory<T>): T {
    ...
}

loadFromJSON(Person) // 將伴生對(duì)象實(shí)例傳入函數(shù)中

注意械巡,Person 類的名字被用作 JSONFactory 的實(shí)例刹淌。

另外,我們還可以為半生對(duì)象定義擴(kuò)展函數(shù)讥耗。

class Person(val firstName: String, val lastName: String) {
    companion object { // 聲明一個(gè)空的伴生對(duì)象
    }
}

// client/server communication module
fun Person.Companion.fromJSON(json: String): Person { // 聲明一個(gè)擴(kuò)展函數(shù)
    ...
}

val p = Person.fromJSON(json)
匿名內(nèi)部類
fun countClicks(window: Window) {
    var clickCount = 0 // 聲明局部變量

    window.addMouseListener(object : MouseAdapter() {
        override fun mouseClicked(e: MouseEvent) {
            clickCount++ // 更新變量的值
        }
    })
    // ...
}

與 Java 的匿名類一樣有勾,在對(duì)象表達(dá)式中的代碼可以訪問創(chuàng)建它的函數(shù)中的變量。但是與 Java 不同古程,訪問并沒有被限制在 final 變量柠衅;你還可以在對(duì)象表達(dá)式中修改變量的值。例如籍琳,我們來看看你可以怎樣使用監(jiān)聽器對(duì)窗口點(diǎn)擊計(jì)數(shù)菲宴。

fun countClicks(window: Window) {
    var clickCount = 0 // 聲明局部變量

    window.addMouseListener(object : MouseAdapter() {
        override fun mouseClicked(e: MouseEvent) {
            clickCount++ // 更新變量的值
        }
    })
    // ...
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市趋急,隨后出現(xiàn)的幾起案子喝峦,更是在濱河造成了極大的恐慌,老刑警劉巖呜达,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谣蠢,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡查近,警方通過查閱死者的電腦和手機(jī)眉踱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來霜威,“玉大人谈喳,你說我怎么就攤上這事「昶茫” “怎么了婿禽?”我有些...
    開封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)大猛。 經(jīng)常有香客問我扭倾,道長(zhǎng),這世上最難降的妖魔是什么挽绩? 我笑而不...
    開封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任膛壹,我火速辦了婚禮,結(jié)果婚禮上唉堪,老公的妹妹穿的比我還像新娘模聋。我一直安慰自己,他們只是感情好巨坊,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開白布撬槽。 她就那樣靜靜地躺著,像睡著了一般趾撵。 火紅的嫁衣襯著肌膚如雪侄柔。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天占调,我揣著相機(jī)與錄音暂题,去河邊找鬼。 笑死究珊,一個(gè)胖子當(dāng)著我的面吹牛薪者,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播剿涮,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼言津,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼攻人!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起悬槽,我...
    開封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤怀吻,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后初婆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蓬坡,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年磅叛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了屑咳。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡弊琴,死狀恐怖兆龙,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情访雪,我是刑警寧澤详瑞,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站臣缀,受9級(jí)特大地震影響坝橡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜精置,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一计寇、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧脂倦,春花似錦番宁、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至火欧,卻和暖如春棋电,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背苇侵。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工赶盔, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人榆浓。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓于未,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子烘浦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容