重學 Kotlin —— object,史上最 「快」 單例 ?

公衆號歷史文章不支持修改,掘金分類功能太拉胯,我會在 小專欄 上長期維護 重學 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

目錄

  1. object 有哪些用法?

  2. 對象聲明 —— 一個關鍵字實現單例 ?

  3. 伴生對象 —— static 的代替者 ?

  4. 對象表達式 —— Kotlin 的匿名內部類 ?

  5. 這究竟是哪一種用法 ?

正文

object 的三種用法

Kotlin 的 object 關鍵字有三種用法:

  • 對象聲明 ,通常用來實現單例
  • 伴生對象 ,相似 Java 的 static 關鍵字,也能夠用於工廠方法模式
  • 對象表達式 ,通常用來代替 Java 的匿名內部類

下面就逐個來看看這三種用法的本質。

對象聲明

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 排版

我是秉心說,關注我,不迷路!

相關文章
相關標籤/搜索