公衆號歷史文章不支持修改,掘金分類功能太拉胯,我會在 小專欄 上長期維護 重學 Kotlin 系列文章。web
本文永久更新地址: xiaozhuanlan.com/topic/43198…安全
這裏是專欄 重學 Kotlin,靈感來自於 Medium 上 Android Developers 團隊的 Kotlin Vocabulary 。編輯器
做爲一名 Kotlin 老鐵粉,我可能在博客裏不止一次的表達過對 Kotlin 的態度。ide
都 2020 了,做爲一名安卓開發者,再不會 Kotlin ,真的說不過去了!函數
介紹 Kotlin 語法的文章不少,那麼,在這個系列中,我會寫一些什麼呢?學習
Kotlin 再強大,也逃脫不了在 JVM 上運行。通過 kotlinc
編譯以後,生成的依舊是 .class
文件。flex
因此,學習 Kotlin 的最佳方式其實就是查看字節碼。Android Studio 直接提供了插件,按以下方式便可查看:ui
Tools -> Kotlin -> Show Kotlin Bytecodethis
固然,字節碼可讀性太差,IDE 提供了 Decompile ,將字節碼轉換成 Java 代碼。url
這樣,咱們就能夠輕鬆掌握 Kotlin 各類語法的本質。
本系列的每一篇文章都會選擇一個關鍵字或者知識點,剖析本質,幫助你們快速深刻理解 Kotlin 。
下面就進入今天的主角 object 。
object 有哪些用法?
對象聲明 —— 一個關鍵字實現單例 ?
伴生對象 —— static 的代替者 ?
對象表達式 —— Kotlin 的匿名內部類 ?
這究竟是哪一種用法 ?
object 的三種用法
Kotlin 的 object
關鍵字有三種用法:
下面就逐個來看看這三種用法的本質。
object
的語義是這樣的: 定義一個類並建立一個實例 。不論是對象聲明,仍是下面會說到的另外兩種用法,都是遵循這一語義的。
做爲對象聲明,它能夠直接用來實現單例模式:
object Singleton{
fun xxx(){} } 複製代碼
話很少說,直接 Decompile 看 Java 代碼:
public final class Singleton {
public static final Singleton INSTANCE; public final void xxx() { } private Singleton() { } static { Singleton var0 = new Singleton(); INSTANCE = var0; } } 複製代碼
從 Java 代碼中能夠看出來,顯然這是一個單例模式。
這裏插播一個問題,static 代碼塊在什麼時候執行?
首先類加載階段能夠分爲加載、驗證、準備、解析、初始化、使用、卸載 七個步驟 。static 代碼塊就是在 初始化 階段執行的。那麼,哪些場景會觸發類的初始化呢?有以下幾種場景:
new
實例化對象
按照上面反編譯出來的 Java 代碼,得到單例對象的方法是 Singleton.INSTANCE
,即調用 Singleon
類的靜態字段 INSTANCE
,就會觸發類的初始化階段,也就觸發了 static 代碼塊的執行,從而完成了單例對象的實例化。同時,因爲類加載過程天生線程安全,因此 Kotlin 的 object 單例活脫脫的就是一個線程安全的懶漢式單例(訪問時初始化)。
此外,object 聲明的單例類和普通類同樣,能夠實現接口,繼承類,也能夠包含屬性,方法。可是它不能由開發者手動聲明構造函數,從反編譯出來的 Java 代碼能夠看到,它只有一個 private
構造函數。
因此,這對實際的業務場景是有必定限制的。對於須要攜帶參數的單例類,object 就有點力不從心了。固然也不難解決,模仿 Java 的寫法就好了,這裏以 DCL 模式爲例。
class Singleton private constructor(private val param: Int) {
companion object { @Volatile private var instance: Singleton? = null fun getInstance(property: Int) = instance ?: synchronized(this) { instance ?: Singleton(property).also { instance = it } } } } 複製代碼
說到這,你應該瞭解了 object
實現單例模式的本質。下面來看看 伴生對象 。
你能夠回想一下,你在 Kotlin 中使用過 static
關鍵字嗎?答案確定是沒有。一般咱們能夠在頂層文件中直接定義常量和頂層函數,但有的時候咱們的確須要在類中定義靜態常量或函數,這樣顯得更加直觀。這就是 伴生對象 的應用場景。
伴生對象,顧名思義,就是伴隨着類而存在的對象,在類加載的時候初始化。
class User(val male: Int){
companion object { val MALE = 0 fun isMale(male:Int) = male == MALE } } 複製代碼
這樣就能夠像調用 static 同樣調用伴生對象中的屬性和函數,而無需創造類實例。
User.MALE
User.isMale(1) 複製代碼
仍是直接看 Java 代碼。
public final class User {
private final int male; private static final int MALE = 0; public static final User.Companion Companion = new User.Companion((DefaultConstructorMarker)null); public final int getMale() { return this.male; } public User(int male) { this.male = male; } public static final class Companion { public final int getMALE() { return User.MALE;public static final User.Companion Companion = new User.Companion((DefaultConstructorMarker)null); } public final boolean isMale(int male) { return male == ((User.Companion)this).getMALE(); } private Companion() { } // $FF: synthetic method public Companion(DefaultConstructorMarker $constructor_marker) { this(); } } } 複製代碼
編譯器爲咱們生成了一個叫作 Companion
的靜態內部類,注意它的 getMale()
和 isMale()
方法並非靜態方法,因此實際去訪問的時候仍是須要一個 Companion
實例的。這裏實例就是 User
類中定義的靜態成員變量 Companion
:
public static final User.Companion Companion = new User.Companion((DefaultConstructorMarker)null);
複製代碼
看到靜態字段,又該想到在類加載的時候初始化的了。那麼,哪一個操做觸發了類加載呢?咱們來反編譯一下 User.MALE
的 Java 代碼。
User.Companion.getMALE();
複製代碼
因此也是訪問時時會初始化伴生對象。再回想一下前面說過的,
object
其實咱們能夠把它理解成 定義一個類並建立一個實例 。
伴生對象仍舊符合這一語義。
在 Java 中如何調用伴生對象呢?User.Companion.isMale(1)
便可。另外,咱們能夠給伴生對象命名,以下所示:
companion object X { ... }
複製代碼
那麼,編譯器生成的類就不是 Companion
了,而是 X
。在 Java 中就能夠用 User.X.isMale(1)
了。
瞭解了伴生對象的本質以後,再來講兩個它的其餘用法。
interface Car {
val brand: String companion object { operator fun invoke(type: CarType): Car { return when (type) { CarType.AUDI -> Audi() CarType.BMW -> BMW() } } } } 複製代碼
這裏重載了 invoke()
方法,調用時直接 Car(CarType.BMW)
便可。你能夠試着用 Java 代碼實現上面的邏輯,對比一下。
伴生對象也是支持擴展方法的。仍是以上面的 Car
爲例,定義一個根據汽車品牌獲取汽車類型的擴展方法。
fun Car.Companion.getCarType(brand:String) :CarType { ...... }
複製代碼
雖然是在 Car.Companion
上定義的擴展函數,但實際上至關於給 Car
增長了一個靜態方法,使用方式以下:
Car.getCarType("BMW")
複製代碼
對象表達式最經典的用法就是用來 代替 Java 的匿名內部類 。例如常見的點擊事件:
xxx.setOnClickListener(object : View.OnClickListener{
override fun onClick(v: View) { } }) 複製代碼
這和 Java 的匿名內部類是等價的。只不過像上面的單方法接口,咱們不多用 object
寫,而是用 lambda
代替,顯得更加簡潔。
xxx.setOnClickListener { view -> ...... }
複製代碼
當匿名對象須要重寫多個方法時,就只能選擇對象表達式了。
和 Java 的匿名內部類同樣,對象聲明中也能夠訪問外部變量。
對象表達式應該是 object
最樸實無華的使用方式了。
看到這裏,你應該已經徹底掌握了 object
關鍵字的本質。那麼,我也要來考考你,仔細看下面的代碼:
class MainActivity : AppCompatActivity() {
val a = 1 object click : View.OnClickListener { override fun onClick(v: View) { val b = a + 1 } } } 複製代碼
object
的用法屬於哪種?
在評論區留下你的答案吧!
本文使用 mdnice 排版
我是秉心說,關注我,不迷路!