本篇內(nèi)容清單如下:
- 類:聲明方式殿遂、實例創(chuàng)建、類成員乙各、構(gòu)造函數(shù)(1 主墨礁、n 次)
- 繼承:覆蓋方法、覆蓋屬性耳峦、類初始化順序
- 屬性:幕后字段恩静、幕后屬性
- 接口
- 可見修飾符
- 擴(kuò)展:擴(kuò)展函數(shù)、擴(kuò)展屬性蹲坷、伴生對象
- 泛型
- 類的形式:伴生對象驶乾、對象表達(dá)式與對象聲明、抽象類循签、數(shù)據(jù)類级乐、嵌套類、內(nèi)部類县匠、匿名內(nèi)部類风科、枚舉類、匿名類乞旦、內(nèi)聯(lián)類
- 委托:屬性委托(動態(tài)綁定 & 靜態(tài)綁定)
- 委托屬性(沒太懂)
一共 16 點內(nèi)容贼穆,原文檔中更細(xì),為重點篇章兰粉,初學(xué)有很多陌生概念故痊。有些可能也不是太理解,不知使用場景玖姑,如 函數(shù)式(SAM)接口愕秫;有些新知識點用起來會彌補(bǔ) Java 不能實現(xiàn)的一些缺憾,感覺非常不錯焰络,如 擴(kuò)展戴甩。
一、類與繼承
1. 類
1.1 類聲明
- 使用關(guān)鍵字
class
聲明舔琅。 - 類聲明 = 類名 + 類頭(指定其類型參數(shù)等恐、主構(gòu)造函數(shù)等)[可選]+ 花括號包圍的類體[可選]
1.2 構(gòu)造函數(shù)
- 一個 主構(gòu)造函數(shù):類頭的一部分,跟在類名后面备蚓。
- 多個 次構(gòu)造函數(shù):同一般函數(shù)课蔬,屬于 類體 的一部分。
主構(gòu)造函數(shù)
- 使用
constructor
關(guān)鍵字郊尝,若主構(gòu)造函數(shù)沒有 注解或可見性修飾符 二跋,則可以省略constructor
。 - 一個類僅一個流昏。
- 不能夠包含任何代碼扎即。
- 初始化代碼可放在
init
關(guān)鍵字為前綴的 初始化塊 中吞获;
// 主構(gòu)造函數(shù)語法
class Person(name: String) { ... }
// 訪問主構(gòu)造的參數(shù)
class Person2(name: String) {
// 1. 在 類體 內(nèi)聲明的 屬性初始化器 中使用 主構(gòu)造的參數(shù)
val introduction = "My name is $name"
// 2. 在 init 初始化塊 中使用 主構(gòu)造的參數(shù)
init {
println("name = $name")
}
}
// 簡潔語法:聲明屬性 && 初始化主構(gòu)造函數(shù)的屬性
class Person3(val name: String, var age: Int) { ... }
次構(gòu)造函數(shù)
- 類若有一個主構(gòu)造函數(shù),則每個 次構(gòu)造函數(shù)谚鄙,必須 委托給 主構(gòu)造函數(shù):可直接委托各拷,或間接通過其他次構(gòu)造函數(shù)委托。
- 委托使用
this
關(guān)鍵字闷营。
class Person(val name: String) {
val children: MutableList<Person> = mutableListOf()
constructor(name: String, parent: Person) : this(name) {
parent.add(this)
}
}
Tips:
-
init
初始化塊中的代碼烤黍,實際上會是 主構(gòu)造函數(shù)的一部分所以,若主構(gòu)造函數(shù)被調(diào)用傻盟,則init
初始化塊 與 屬性初始化器 都會一起被執(zhí)行速蕊。所以即使沒有主構(gòu)造,次構(gòu)造函數(shù)這種委托仍會隱式發(fā)生娘赴,仍會執(zhí)行初始化塊规哲。 - 若一個 非抽象類 沒有任何 構(gòu)造函數(shù),均會有一個生產(chǎn)的 不帶參數(shù)的主構(gòu)造函數(shù)诽表。
- 若希望類的構(gòu)造函數(shù)為私有唉锌,則使用
private
修飾 主構(gòu)造函數(shù)。
1.3 創(chuàng)建類的實例
- Kotlin 沒有
new
關(guān)鍵字关顷。 - 創(chuàng)建一個類的實例糊秆,就像 普通函數(shù) 一樣調(diào)用 構(gòu)造函數(shù)武福。
val person = Person("Coral")
val demo = Demo()
1.4 類成員
類 可以包括:
- 構(gòu)造函數(shù) 與 初始化塊
- 函數(shù)
- 屬性
- 嵌套類與內(nèi)部類 [1]
- 對象聲明 [2]
2. 繼承
- Kotlin 中所有類都有一個共同的超類
Any
议双,等同于 Java Object 。 -
Any
有三個方法:equals()捉片、hashCode() 與 toString() 平痰。 -
默認(rèn)情況下,Kotlin 類是
final
的伍纫,不能被繼承宗雇,要使類可繼承,使用open
關(guān)鍵字標(biāo)記莹规。 - 若需聲明一個顯式的超類類型赔蒲,則為
class Derived(p: Int) : Base(p)
。 - 若子類有一個 主構(gòu)造函數(shù)良漱,其 父類舞虱,必須用 子類的主構(gòu)造函數(shù)的參數(shù) 就地初始化。
-
若子類沒有 主構(gòu)造函數(shù)母市,則子類每個 次構(gòu)造函數(shù) 必須使用
super
關(guān)鍵字 初始化其 父類類型 或 委托給另一個構(gòu)造函數(shù)矾兜。 如:class MyView : View { constructor(ctx: Context) : super(ctx) }
。
2.1 覆蓋方法
- 必須 類 是
open
可繼承的患久,方法 添加open
修飾才有效椅寺,才可被子類覆蓋浑槽。【類可繼承成員才允許開放繼承】 - 被覆蓋的方法返帕,也必須加上
override
修飾符桐玻,否則,編譯器會報錯荆萤』澹【覆蓋方法必須顯式添加 override】 - 若方法沒有標(biāo)識
open
,則子類不允許定義相同簽名的函數(shù)观腊,無論有沒有override
邑闲。【普通函數(shù)不能被繼承】 - 若標(biāo)記為
override
的成員梧油,默認(rèn)是一直可向下被覆蓋的苫耸。若想禁止再次覆蓋,可使用final
關(guān)鍵字修飾儡陨⊥首樱【final 可禁止覆蓋傳遞】
open class Shape {
open fun draw() { // 可被覆蓋 }
open fun color() { }
fun fill() { // 不可覆蓋,子類也不允許有同簽名方法 }
}
class Circle() : Shape() {
override fun draw() { // 覆蓋方法 }
final override fun color() { // 禁止再次覆蓋 }
}
2.2 覆蓋屬性
- 同 覆蓋方法 類似骗村。子類重寫的屬性必須要以
override
開頭嫌褪,并且類型要兼容。 - 每個聲明的屬性 可由 具有 初始化器 的屬性 或
get
方法的屬性覆蓋胚股。 - 可以用一個
var
覆蓋一個val
屬性笼痛,反之不行。因為 var 有 get() 和 set() 方法琅拌,而 val 只有 get() 方法缨伊。 - 可以在 主構(gòu)造函數(shù) 中使用
override
關(guān)鍵字 作為 屬性聲明的一部分。
open class Shape {
open val vertexCount: Int = 0
open val borderColor: String = ""
open val bgColor: String = ""
}
class Rectangle(override val vertexCount: Int = 4) : Shape() { // 在主構(gòu)造覆蓋屬性
override val borderColor: String = "#000000"
override var bgColor = "#ffffff" // 使用 var 覆蓋 val 屬性
}
2.3 派生類初始化順序
構(gòu)造派生類實例過程:
- 第一步:完成其基類的初始化(初始化塊进宝、屬性構(gòu)造器)
- 第二步:初始化派生類中 聲明或覆蓋的屬性刻坊。
Tips:
- 若在 基類初始化 邏輯中,使用了任何 派生類中聲明或覆蓋的屬性党晋,則可能會導(dǎo)致不正確的行為或 運行時故障(因為此時派生類該屬性還未初始化)谭胚。
- 設(shè)計一個基類時,應(yīng)避免在 構(gòu)造函數(shù)未玻、屬性初始化器 以及 init 塊中使用
open
成員灾而。
2.4 調(diào)用超類實現(xiàn)
- 派生類中 可以使用
super
關(guān)鍵字 調(diào)用其超類的函數(shù)與屬性訪問器的實現(xiàn)。 - 在一個 內(nèi)部類 中訪問 外部類的超類深胳,可通過由 外部類名限定的
super
關(guān)鍵字來實現(xiàn):super@Outer
绰疤,如在 Rectangle 類的 inner class 某個方法中調(diào)用:super@Rectangle.draw()
。
2.5 覆蓋規(guī)則
- 若一個類 從它的直接超類 繼承 相同成員的多個實現(xiàn)舞终,則必須覆蓋這個成員 并提供自己的實現(xiàn)轻庆。
- 為表示從 哪個超類型繼承的實現(xiàn) 以消除歧義癣猾,使用 尖括號中超類型名限定的
super
,如super<Base>
余爆。
open class R1 {
open fun draw() { }
}
// Tips:接口成員默認(rèn)是 "open" 的
interface P1 {
fun draw() { }
}
// Tips:繼承父類纷宇,必須要帶上父類的構(gòu)造方法,有參或無參構(gòu)造
class S1 : R1(), P1 {
override fun draw() {
super<P1>.draw() // 調(diào)用 P1.draw()
super<R1>.draw() // 調(diào)用 R1.draw()
}
}
3. 屬性
- 屬性可使用關(guān)鍵字
var
聲明為可變的蛾方,也可以是val
聲明為只讀的像捶。 - 屬性 初始化器、getter 和 setter 都是可選的桩砰。
3.1 幕后字段
舉個例子拓春,就很容易知道幕后字段如何使用,并且對比 Java 的使用場景亚隅,就知道 幕后字段 使用場景也非常多硼莽。
var counter = 0
set(value) {
this.counter = value
}
上述代碼運行后,會出現(xiàn) stackOverflow煮纵,這是為啥懂鸵?可轉(zhuǎn)換為 Java 代碼,set() 里面賦值的那句行疏,直接等價于重新調(diào)用了 set() 方法匆光,如此就出現(xiàn)了 自己調(diào)用自己,就出現(xiàn)棧溢出錯誤了酿联。
為了解決上述問題终息,便可以使用 幕后字段 。
關(guān)于某后字段總結(jié)如下:
- 使用
field
標(biāo)識符在 訪問器中引用货葬,并且只能在 屬性的訪問器內(nèi)使用采幌。 - 并不是所有屬性都會有幕后字段劲够,需滿足以下條件之一:1)屬性至少一個訪問器使用默認(rèn)實現(xiàn)震桶;2)自定義訪問器通過
field
引用幕后字段。 - 自定義屬性時征绎,借助其他屬性賦值蹲姐,就沒有 幕后字段。
// 正確寫法
var counter = 0
get() = field
set(value) {
field = value
}
// 以下 isEmpty 沒有幕后字段人柿,因為操作的不是字段本身
val isEmpty: Boolean
get() = this.size == 0
set(value) {
this.size = 0
}
3.2 幕后屬性
- 有時候有這種需求柴墩,我們希望一個屬性:對外表現(xiàn)為只讀,對內(nèi)表現(xiàn)為可讀可寫凫岖,我們將這個屬性成為 幕后屬性江咳。
// _table 為 幕后屬性,僅內(nèi)部操作哥放,table 為對外訪問屬性
private var _table: Map<String, Int>? = null
public val table: Map<String, Int>
get() {
if (_table == null) {
_table = HashMap() // 類型參數(shù)已推斷出
}
return _table ?: throw AssertionError("Set to null by other thres")
}
備注:一開始不太理解幕后字段 & 幕后屬性這兩個概念的歼指,詳解與使用場景可查看:http://www.reibang.com/p/c1a4c04eb33c
3.3 編譯期常量
- 定義:只讀屬性 的值在編譯期是已知的爹土,可以使用
const
修飾符標(biāo)記。
該屬性需滿足條件: - 位于頂層 或
object 聲明
或companion object
的一個成員 - 以
String
或 原生類型值初始化 - 沒有自定義
getter
// 對比 Java
private final String a = "A";
private final static String b = "B";
public final static String c = "C";
// 轉(zhuǎn)換為 Kotlin
val a = "A"
companion object {
val b = "B"
const val c = "C" // public 常量才能用 const 標(biāo)記
}
3. 接口
- 關(guān)鍵字
interface
定義接口 - 一個 類或?qū)ο?/strong> 可實現(xiàn)多個接口 [3]
- 可在接口中定義屬性踩身。在接口中聲明的屬性胀茵,要么是抽象的,要么提供訪問器實現(xiàn)挟阻。另 該屬性不能有 幕后字段,因此訪問器不能引用。
3.1 函數(shù)式(SAM)接口
TODO 暫時不理解定義與使用場景好渠。
4. 可見性修飾符
省略文虏。
5. 擴(kuò)展
- 能擴(kuò)展一個 類的新功能 而無需 繼承該類 或 使用像裝飾者這樣的設(shè)計模式。如坷备,可為不能修改的第三方庫中的類編寫一個新的函數(shù)挪拟。
- 擴(kuò)展函數(shù):為類新增函數(shù)
- 擴(kuò)展屬性:為類新增屬性
- 擴(kuò)展 不能真正修改它們所擴(kuò)展的類,為 靜態(tài)分發(fā)击你。如出現(xiàn)同 覆蓋方法相同簽名的 擴(kuò)展函數(shù)玉组,則具體調(diào)用哪個方法,取決于 對象類型是 哪一個丁侄。擴(kuò)展方法惯雳,調(diào)用時,類型必須同聲明一致鸿摇,沒有繼承的關(guān)系石景。
- 同簽名的 成員函數(shù) 與 擴(kuò)展函數(shù) 調(diào)用,則調(diào)用的一定個是 成員函數(shù)拙吉。
6. 類的幾種表現(xiàn)形式
6.1 伴生對象
- 類內(nèi)部的對象聲明可以用
companion
關(guān)鍵字標(biāo)記潮孽。 - 可以將其寫成 該類內(nèi) 對象聲明 中的一員。
- [ 在 類內(nèi) 聲明了一個 伴生對象筷黔,就可以訪問其成員往史,只是以 類名 作為 限定符。]
- 伴生對象 看起來像其他語言的靜態(tài)成員佛舱,在運行時仍是 真實對象 的實例成員椎例,并且可以實現(xiàn)接口。
創(chuàng)建一個類實例请祖,但是不用顯式聲明新的子類订歪,使用 對象表達(dá)式 和 對象聲明 處理。
6.2 對象表達(dá)式
- 和 Java 以匿名方式構(gòu)造 抽象類 實例很像肆捕。
6.3 對象聲明
- 單例模式 中常用
- Kotlin 對 單例 聲明 非常容易刷晋,總是在
object
關(guān)鍵字后跟一個名稱。 - 就像變量聲明一樣,但不是一個表達(dá)式眼虱,不能用作賦值或舞。
- 對象聲明 的初始化過程是 線程安全 的并且在首次訪問時進(jìn)行。
上述三者之間的語義差別:
- 對象表達(dá)式 是在使用他們的地方 立即 執(zhí)行(及初始化的)蒙幻;
- 對象聲明 是在第一次被訪問到時 延遲 初始化的映凳;
- 伴生對象 是在相應(yīng)的類被加載(解析)時初始化的,與 Java 靜態(tài)初始化器的語義相匹配邮破。
代碼 DEMO:
// 1. 對象表達(dá)式
// Demo01:創(chuàng)建一個繼承自 某個/某些 類型的匿名類的對象
abstract class BaseAdapter {
abstract fun getCount(): Int
}
class MyListView {
// 初始化一個 adapter诈豌,對象表達(dá)式 object : BaseAdapter
var adapter: BaseAdapter = object : BaseAdapter() {
override fun getCount(): Int = 3
}
}
// 2. 對象表達(dá)式
class DataProvider
// 對象表達(dá)式,object 類名 {} 可以在另一個文件
object DataManager {
fun register(provider: DataProvider) {
//
}
val providers: Collection<DataManager>
get() {
TODO()
}
}
fun testSingleton() {
DataManager.register(DataProvider())
}
/**
* 3. 伴生對象
* - 類內(nèi)部的對象聲明 可以用 companion 關(guān)鍵字標(biāo)記抒和。
*/
class MyCompanion {
companion object Factory {
fun create(): MyCompanion = MyCompanion()
val globalCount: Int = 0
}
}
// Tips:伴生對象的成員 可通過 只是用類名 作為限定符來調(diào)用
val instance = MyCompanion.create()
val count = MyCompanion.globalCount
6.4 抽象類
- 類 及 其中某些成員聲明為
abstract
矫渔。 - 并不需要用
open
標(biāo)注一個 抽象類或函數(shù)。 - 可以用 一個抽象成員 覆蓋 一個非抽象的開放成員摧莽。
open class Polygon {
open fun draw() {}
}
abstract class Rectangle : Polygon() {
abstract override fun draw()
}
6.5 數(shù)據(jù)類
- 使用
data
標(biāo)記庙洼。只保存數(shù)據(jù)的類
編譯器會自動從主構(gòu)造函數(shù)中聲明的屬性導(dǎo)出/生成以下成員:
- equals() / hashCode 對
- toString() 格式為:
User(name=John, age = 42)
- copy() 函數(shù)
為生成合理的代碼,數(shù)據(jù)類滿足條件:
- 主構(gòu)造函數(shù) 至少一個參數(shù)镊辕;
- 主構(gòu)造函數(shù) 搜索頁參數(shù)都需要標(biāo)記為
val
或var
油够; - 數(shù)據(jù)類不能是 abstract、open征懈、sealed 或 inner石咬;
- (1.1 之前) 數(shù)據(jù)類只能實現(xiàn)接口。
6.6 密封類
- 使用
sealed
修飾符卖哎。 - 用來表示 受限的類繼承結(jié)構(gòu):當(dāng)一個值為 有限幾種的類型鬼悠、而不能有任何其他類型時。某種意義上亏娜,是 枚舉類 的擴(kuò)展焕窝。
- 密封類 vs 枚舉類:每個枚舉常量只存在一個實例,而密封類一個子類可包含狀態(tài)的多個實例维贺。
- 子類必須與密封類在相同的文件中聲明它掂。
- 一個密封類自身是 抽象的,不能實例化 并且 可以有 抽象(
abstract
)成員幸缕。 - 密封類 不允許 有非
private
構(gòu)造函數(shù)(默認(rèn)為 private)群发。 - 擴(kuò)展 密封類子類的類可以放在任何位置,無需同一個文件中发乔。
6.7 嵌套類與內(nèi)部類
- 嵌套類:類可嵌套在其他類中:類、接口可相互嵌套雪猪。
- 內(nèi)部類:使用
inner
標(biāo)記的嵌套類栏尚,能訪問外部類的成員,因為會帶有一個外部類的對象引用只恨。 - 匿名類:
- 匿名內(nèi)部類:使用 對象表達(dá)式 創(chuàng)建匿名內(nèi)部類實例(Java 抽象類)译仗。若為接口抬虽,則可以使用 lambda 表達(dá)式創(chuàng)建(Java 接口)。
6.8 枚舉類
- 每個枚舉常量都是一個對象纵菌,用 逗號 分隔阐污。
- 每個枚舉都是 枚舉類 的實例,可以如此初始化:
enum class Color(val rgb: Int) {
RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000FF),
}
6.9 內(nèi)聯(lián)類
僅在 Kotlin 1.3 版本之后才可用咱圆,目前處于 Alpha 版本笛辟。
- 使用
inline
修飾符聲明。
內(nèi)聯(lián)類成員限制:
- 內(nèi)聯(lián)類 不能有
init
代碼塊 - 不能含有 幕后字段(因此只能有簡單的計算屬性)
7. 泛型
TODO
8. 委托
8.1 委托屬性
有些屬性類型序苏,隨每次可手動調(diào)用手幢,但是能實現(xiàn)一次并放入一個庫會更好。例如包括:
- 延遲屬性:其值只在首次訪問時計算忱详;
- 可觀察屬性:監(jiān)聽器會收到有關(guān)此屬性變更的通知围来;
- 把每個屬性儲存在一個 map 中,而不是每個存在于
單獨的字段中匈睁。
為了涵蓋上述情況及其他监透,Kotlin 支持 委托屬性:
class Demo {
var p: String by Delegate()
}
語法:val/var <屬性名>: <類型> by <表達(dá)式> 。 by
后面的就是該 委托航唆。