接口
接口的方法可以有一個(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
, protected
和 private
修飾符蒲犬。
但是默認(rèn)的可見性是不一樣的:如果你省略了修飾符,聲明就是
public
的澄成。Java 中的默認(rèn)可見性——包私有,在 Kotlin 中并沒有使用畏吓。
在 Java 中,你可以從同一個(gè)包中訪問一個(gè)
protected
的成員卫漫,但 Kotlin 中protected
成員只在類和它的子類中可見菲饼。要注意的是類的擴(kuò)展函數(shù)不能訪問它的
private
和protected
成員。
嵌套類
默認(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@Outer
從 Inner
類去訪問 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
貌踏,equals
和 hashCode
。
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 是感受到最需要注意的地方漂问,
object
在Kotlin
中的用法和重要赖瞒。
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++ // 更新變量的值
}
})
// ...
}