接口
接口的方法可以有一個默認(rèn)實現(xiàn)
interface Clickable {
fun click() // 普通的方法聲明
fun showOff() = println("I'm clickable!") // 帶默認(rèn)實現(xiàn)的方法
}
如果你實現(xiàn)了這個接口,并且對默認(rèn)行為感到滿意的話可以省略 showOff的實現(xiàn),但你需要為 click 提供一個實現(xiàn)。
如果另外一個借口也有一個同樣的方法showOff
interface Focusable {
fun setFocus(b: Boolean) = println("I ${if (b) "got" else "lost"} focus.")
fun showOff() = println("I'm focusable!")
}
如果你需要在你的類中實現(xiàn)這兩個接口會發(fā)生什么?
如果你沒有顯式實現(xiàn) showOff,你會得到如下的編譯錯誤:
The class 'Button' must override public open fun showOff() because it inherits many implementations of it.
Kotlin 編譯器強制要求你提供你自己的實現(xiàn)。
現(xiàn)在 Button 類實現(xiàn)了兩個接口。你通過調(diào)用繼承的兩個父類型中的實現(xiàn)來實現(xiàn)自己的 showOff()
class Button : Clickable, Focusable {
override fun click() = println("I was clicked")
override fun showOff() { // 如果同樣的繼承成員有不止一個實現(xiàn),你必須提供一個顯式實現(xiàn)。
super<Clickable>.showOff()
super<Focusable>.showOff() // 使用尖括號加上父類型名字的 "super" 表明了你想要調(diào)用哪一個父類的方法
}
}
Java 中你可以把基類的名字放在 super 關(guān)鍵字的前面,就像 Clickable.super.showOff() 這樣,在 Kotlin 中需要把基類的名字放在尖括號中:super<Clickable>.showOff()
類
繼承
如果你想允許創(chuàng)建一個類的子類,你需要使用 open 修飾符來標(biāo)示這個類。此外,你需要給每一個可以被重寫的屬性或方法添加 open 修飾符。
open class RichButton : Clickable { // 這個類是open的:其他類可以繼承它。
fun disable() {} // 這個函數(shù)是final的:你不能在子類中重寫它。
open fun animate() {} // 這個函數(shù)是open的:你可以在子類中重寫它。
override fun click() {} // 這個函數(shù)重寫了一個開放函數(shù)并且它本身同樣是open的。
}
注意,如果你重寫了一個基類或者接口的成員,重寫了的成員同樣默認(rèn)是
open。如果你想改變這一行為,阻止你的類的子類重寫你的實現(xiàn),你可以顯式將重寫的成員標(biāo)注為final。open class RichButton : Clickable { final override fun click() {} }
在 Kotlin 中,同 Java 一樣,你可以將一個類聲明為 abstract ,抽象類始終是開放的,所以你不需要顯式使用 open 修飾符。
abstract class Animated { // 這個類是抽象的:你不能創(chuàng)建它的實例。
abstract fun animate() // 這個函數(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 中,你可以從同一個包中訪問一個
protected的成員,但 Kotlin 中protected成員只在類和它的子類中可見。要注意的是類的擴展函數(shù)不能訪問它的
private和protected成員。
嵌套類
默認(rèn)情況下Kotlin 的嵌套類不能訪問外部類的實例,而 Java 中可以
Kotlin 中沒有顯式修飾符的嵌套類與 Java 中的
static嵌套類是一樣的-
要把它變成一個內(nèi)部類來持有一個外部類的引用的話你需要使用
inner修飾符類 A 在另一個類 B 中聲明 在 Java 中 在 Kotlin 中 嵌套類(不存儲外部類的引用) static class A class A 內(nèi)部類(存儲外部類的引用) class A inner class A
在 Kotlin 中引用外部類實例的語法也與 Java 不同。你需要使用 this@Outer 從 Inner 類去訪問 Outer 類:
class Outer {
inner class Inner {
fun getOuterReference(): Outer = this@Outer
}
}
密封類(sealed)
父類添加一個 sealed 修飾符,對可能創(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修飾符隱含了這個類是一個開放類;你不再需要顯式添加open修飾符。當(dāng)你在
when中使用sealed類并且添加一個新的子類的時候,有返回值的when表達(dá)式會導(dǎo)致編譯失敗,它會告訴你哪里的代碼必須要修改。Expr類有一個只能在類內(nèi)部調(diào)用的private構(gòu)造方法。你也不能聲明一個sealed接口。為什么?如果你能這樣做的話,Kotlin編譯器不能保證任何人都不能在 Java 代碼中實現(xiàn)這個接口。
構(gòu)造函數(shù)
主構(gòu)造函數(shù)和初始化語句塊
class User constructor(_nickname: String) { // 帶一個參數(shù)的主構(gòu)造方法
val nickname: String
init { // 初始化語句塊
nickname = _nickname
}
}
簡化后
class User(val nickname: String) // "val" 意味著相應(yīng)的屬性會用構(gòu)造方法的參數(shù)來初始化
如果你的類具有一個父類,主構(gòu)造方法同樣需要初始化父類。你可以通過在基類列表的父類引用中提供父類構(gòu)造方法參數(shù)的方式來做到這一點:
open class User(val nickname: String) { ... }
class TwitterUser(nickname: String) : User(nickname) { ... }
如果你沒有給一個類聲明任何的構(gòu)造方法,將會生成一個不做任何事情的默認(rèn)構(gòu)造方法:
open class Button // 將會生成一個不帶任何參數(shù)的默認(rèn)構(gòu)造方法
如果繼承了 Button 類并且沒有提供任何的構(gòu)造方法,你必須顯式調(diào)用父類的構(gòu)造方法即使它沒有任何的參數(shù):
class RadioButton: Button()
這就是為什么在父類名稱后面還需要一個空的括號
注意與接口的區(qū)別:接口沒有構(gòu)造方法,所以在你實現(xiàn)一個接口的時候,你不需要在父類型列表中它名稱后面再加上括號。
數(shù)據(jù)類
如果你為你的類添加 data 修飾符,必要的方法將會自動生成toString,equals 和 hashCode。
data class Client(val name: String, val postalCode: Int)
請注意雖然數(shù)據(jù)類的屬性并沒有必須是 val —— 你同樣可以使用 var —— 但還是強烈推薦只使用只讀屬性,讓數(shù)據(jù)類的實例不可變。
為了讓使用不可變對象的數(shù)據(jù)類變得更容易,Kotlin 編譯器為他們多生成了一個方法copy():一個允許拷貝類的實例的方法,并在拷貝的同時修改某些屬性的值。
類委托:"by" 關(guān)鍵字
下面這段代碼,為了實現(xiàn) Collection借口,你必須實現(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 {}
類中所有的方法實現(xiàn)都消失了,DelegatingCollection默認(rèn)會使用ArrayList的行為,如果你需要修改某一個函數(shù)的行為,只需要重寫這一個函數(shù)即可。
"object"關(guān)鍵字
Tips: 這是我在學(xué)習(xí) Koltin 是感受到最需要注意的地方,
object在Kotlin中的用法和重要。
Kotlin 中 object 關(guān)鍵字在多種情況下出現(xiàn),但是它們都遵循同樣的核心理念:這個關(guān)鍵字定義一個類并同時創(chuàng)建一個實例(換句話說就是一個對象)。讓我們來看看使用它的不同場景:
- 對象聲明是定義單例的一種方式。
- 伴生對象可以持有工廠方法和其他與這個類相關(guān),但在調(diào)用時并不依賴類實例的方法。它們的成員可以通過類名來訪問。
- 對象表達(dá)式用來替代 Java 的匿名內(nèi)部類。
對象聲明
對象聲明將 類聲明 與該類的 單一實例 聲明結(jié)合到了一起。
object Payroll {
val allEmployees = arrayListOf<Person>()
fun calculateSalary() {
for (person in allEmployees) {
...
}
}
}
與變量一樣,對象聲明允許你使用對象名加 . 字符的方式來調(diào)用方法和訪問屬性:
Payroll.allEmployees.add(Person(...))
Payroll.calculateSalary()
你同樣可以在類中聲明對象。這樣的對象同樣只有一個單一實例,它們在每個容器類的實例中并不具有不同的實例
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 對象
Kotlin 中的對象聲明被編譯成了通過靜態(tài)字段來持有它的單一實例的類,這個字段名字始終都是
INSTANCE。如果你在 Java 中實現(xiàn)單例模式,你也許也會順手做同樣地事。因此,要從 Java 代碼使用 Kotlin 對象,可以通過訪問靜態(tài)的INSTANCE字段:/* Java */ CaseInsensitiveFileComparator.INSTANCE.compare(file1, file2);在這個例子中,
INSTANCE字段的類型是CaseInsensitiveFileComparator。
伴生對象companion
Kotlin 中的類不能擁有靜態(tài)成員;Java 的 static 關(guān)鍵字并不是 Kotlin 語言的一部分。使用伴生對象,會讓我們的方法調(diào)用看起來很像static方法。
class A {
companion object {
fun bar() {
println("Companion object called")
}
}
}
>>> A.bar()
Companion object called
重要用途,實現(xiàn)工廠方法模式
class User private constructor(val nickname: String) { // 將主構(gòu)造方法標(biāo)記為私有
companion object { // 聲明伴生對象
fun newSubscribingUser(email: String) = // 聲明一個命名的伴生對象
User(email.substringBefore('@'))
fun newFacebookUser(accountId: Int) = // 用工廠方法通過 Facebook 賬號來創(chuàng)建一個新用戶
User(getFacebookName(accountId))
}
}
你可以通過類名來調(diào)用 companion object 的方法:
>>> val subscribingUser = User.newSubscribingUser("bob@gmail.com")
>>> val facebookUser = User.newFacebookUser(4)
>>> println(subscribingUser.nickname)
bob
作為普通對象使用的伴生對象
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
如果你省略了伴生對象的名字,默認(rèn)的名字將會分配為 Companion。
在伴生對象中實現(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 = ... // 實現(xiàn)接口的伴生對象
}
}
這時,如果你有一個函數(shù)使用抽象方法來加載實體,你可以將 Person 對象傳進(jìn)去。
fun loadFromJSON<T>(factory: JSONFactory<T>): T {
...
}
loadFromJSON(Person) // 將伴生對象實例傳入函數(shù)中
注意,Person 類的名字被用作 JSONFactory 的實例。
另外,我們還可以為半生對象定義擴展函數(shù)。
class Person(val firstName: String, val lastName: String) {
companion object { // 聲明一個空的伴生對象
}
}
// client/server communication module
fun Person.Companion.fromJSON(json: String): Person { // 聲明一個擴展函數(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 的匿名類一樣,在對象表達(dá)式中的代碼可以訪問創(chuàng)建它的函數(shù)中的變量。但是與 Java 不同,訪問并沒有被限制在 final 變量;你還可以在對象表達(dá)式中修改變量的值。例如,我們來看看你可以怎樣使用監(jiān)聽器對窗口點擊計數(shù)。
fun countClicks(window: Window) {
var clickCount = 0 // 聲明局部變量
window.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
clickCount++ // 更新變量的值
}
})
// ...
}