Android Kotlin學習筆記(Java與Kotlin的單例模式比較)

概念引入

Java中最簡單的設計模式之一,這種模式保證創建自身類的對象只有一個,可以直接訪問其中方法自動創建並獲得自身對象,不需要直接實例化。因此,單例模式也是創建者模式的一種。
我感覺我的描述不夠準確,但是這篇文字主要是比較Kotlin與Java單例模式的代碼比較。詳細概念請參考:菜鳥教程
言歸正傳,我們主要從單例模式的類型開始一一比較

1.懶漢式

懶漢式其實分倆種,線程安全的和線程不安全的,區別就是synchronized是否存在。爲什麼叫懶漢式,因爲首次創建時候纔去創建對象,所以他是懶方法,懶加載概念就出來了,lazy loading~

1.1 線程不安全的懶漢式

作爲最基本的實現方式之一,因爲synchronized的鎖關鍵字,所以不能在多線程下工作,線程不安全

Java下的實現

沒必要多說了,直接看代碼實現和註釋。

public class SingletonLazy1 {
    private static SingletonLazy1 instance; //單例靜態變量
    private SingletonLazy1 (){} //構造私有化

    public static SingletonLazy1 getInstance() {
        if (instance == null) {//不存在就創建
            instance = new SingletonLazy1();
        }
        return instance;
    }
}

Kotlin下的實現

原諒我是直接代碼在android studio翻譯過去的,流程入下圖。
Java轉kotlin
首先我們注意Kotlin其中幾個有意思的特性。

  1. 變量會自動創建並隱藏get/set方法,這裏巧妙運用了這點,在get方法中判斷對象是否存在並獲取。
  2. Kt中,object 關鍵字聲明,其內部不允許聲明構造方法,使用有點類似匿名內部類的使用,所以大家一般也是用object來聲明單例。
  3. Object聲明的類,無法創建空的構造函數,所以我們也無法private修飾
object SingletonLazy1 {

    var instance //單例靜態變量
            : SingletonLazy1? = null
        get() {
            if (field == null) { //不存在就創建
                field = SingletonLazy1
            }
            return field
        }
        private set
}

暫且看一看具體到class裏面的實現是什麼樣子的。通過編譯器直接查看class字節碼,Tools→Kotlin→Show Kotlin ByteCodes→新窗口中的Decompile
1
2
得到字節碼如下:

public final class SingletonLazy1 {
   @Nullable
   private static SingletonLazy1 instance;
   public static final SingletonLazy1 INSTANCE;

   @Nullable
   public final SingletonLazy1 getInstance() {
      if (instance == null) {
         instance = INSTANCE;
      }

      return instance;
   }

   private SingletonLazy1() {
   }

   static {
      SingletonLazy1 var0 = new SingletonLazy1();
      INSTANCE = var0;
   }
}

我們會發現會有倆個單例變量,爲什麼呢?這就是由於object關鍵字聲明類似匿名內部類,本身可以作爲單例模式(餓漢模式)。如果僅僅只是實現非線程安全的單例模式,我們僅僅需要Object關鍵字創建類即可。
既然說到懶漢式,我們就暫時拋開object,修改下代碼,改成非線程安全的懶漢模式。代碼如下,注意get()方法命名爲getInstance()時候報錯,我猜是隱藏了同名函數。

class SingletonLazy1 private constructor(){
    companion object {
        private var instance: SingletonLazy1? = null
            get() {
                if (field == null) {
                    field = SingletonLazy1()
                }
                return field
            }

        fun get(): SingletonLazy1 {//起名爲getInstance報錯,估計內部隱藏了同名函數
            return instance!!
        }
    }
}

比葫蘆畫瓢,看下字節碼。刪掉@Metadata等印象閱讀的部分。

public final class SingletonLazy1 {
   private static SingletonLazy1 instance;
   public static final SingletonLazy1.Companion Companion = new SingletonLazy1.Companion((DefaultConstructorMarker)null);

   private SingletonLazy1() {
   }

   // $FF: synthetic method
   public SingletonLazy1(DefaultConstructorMarker $constructor_marker) {
      this();
   }

   public static final class Companion {
      private final SingletonLazy1 getInstance() {
         if (SingletonLazy1.instance == null) {
            SingletonLazy1.instance = new SingletonLazy1((DefaultConstructorMarker)null);
         }
         return SingletonLazy1.instance;
      }

      private final void setInstance(SingletonLazy1 var1) {
         SingletonLazy1.instance = var1;
      }
      
      @NotNull
      public final SingletonLazy1 get() {
         SingletonLazy1 var10000 = ((SingletonLazy1.Companion)this).getInstance();
         if (var10000 == null) {
            Intrinsics.throwNpe();
         }
         return var10000;
      }

      private Companion() {
      }
      // $FF: synthetic method
      public Companion(DefaultConstructorMarker $constructor_marker) {
         this();
      }
   }
}

看到後發現果真有getInstance()的方法。這時候我們可以注意下Kotlin的companion關鍵字,他所修飾的object爲伴生對象,伴生對象在類中只能存在一個,我們可以暫時理解爲companion類似於static修飾。既然我們多寫了等於getInstance()get()方法,雖然無傷大雅,但是憑着精益求精的原則,我們試着幹掉他。理下思路:

  1. 既然我們的get()是多餘的,我們就幹掉他。
  2. 字節碼中getInstance()方法的修飾爲private,外界肯定調用不到,修改爲public
class SingletonLazy1 private constructor(){
    companion object {
        var instance: SingletonLazy1? = null	//Kotlin中public可以隱藏
            get() {
                if (field == null) {
                    field = SingletonLazy1()
                }
                return field
            }
    }
}

如上代碼所示,就是一個完整簡潔的Kotlin實現。大家也可以再去查看下字節碼比對一下。由於companion關鍵字靜態修飾的絕妙,調用也很簡單:SingletonLazy1.Companion.getInstance();

1.1 線程安全的懶漢式

對比線程不安全,僅僅就是在獲取單例的public方法加了synchronized關鍵字鎖住。

Java下的實現

public class Single {
    private static Single instance;////單例靜態變量

    private Single() { }

    public static synchronized Single getInstance() {//synchronized鎖
        if (instance == null) {//不存在創建
            instance = new Single();
        }
        return instance;
    }
}

kotlin下的實現

同樣的轉換爲Kotlin仍然會轉換爲object,算了還是自己手寫吧。其實很簡單,Kotlin中有@Synchronized等同於synchronized關鍵字,可以用來修飾方法。也可以指定@get:Synchronized,
倆種作用一樣的,任選其一。

class SingletonLazy1 private constructor(){

    companion object {
        @get:Synchronized
        var instance: SingletonLazy1? = null
            //@Synchronized
            get() {
                if (field == null) {
                    field = SingletonLazy1()
                }
                return field
            }
    }
}

查看下字節碼比對一下,準確無誤。擡走,下一個。

2.餓漢式

缺點:類加載時就初始化,浪費內存。

它通過類加載時就初始化,機制避免了多線程的同步問題(無鎖,執行效率高),同樣的instance 在類裝載時就實例化,沒有達到 lazy loading 的效果。同時,無論你用或者不用,他就是已經完成初始化了,浪費資源
我們在1.1 線程不安全的懶漢式中已經提到object關鍵字和懶漢式了,這裏就不在文字敘述了,注意object關鍵字,官方文檔看下資料就可以了,直接上代碼。

Java下的實現

public class Single2 {
    private static Single2 instance = new Single2(); 	//變量私有
    private Single2 (){} 	//構造方法私有
    public static Single2 getInstance() {
        return instance;
    }
}

Kotlin下的實現

object Single2{
    fun test(): Unit {}
}

調用也非常簡單Single2.test()

3. DCL

即 double-checked locking,也叫雙檢鎖/雙重校驗鎖倆個關鍵點:

  1. volatile關鍵字。簡單說某個線程改變了其所修飾的變量,立馬在內存中刷新,其他線程同步可知。
  2. synchronized 鎖在其中的機制,鎖的是這個類的操作。

Java下的實現

public class SingleDouble {
    private volatile static SingleDouble singleton;	//	volatile修飾 線程同步

    private SingleDouble() {}//	私有不可訪問

    public static SingleDouble getSingleton() {
        if (singleton == null) {
            synchronized (SingleDouble.class) {// synchronized
                if (singleton == null) {
                    singleton = new SingleDouble();
                }
            }
        }
        return singleton;
    }
}

Kotlin下的實現

直接轉換爲Kotlin,修改object爲class加上私有構造器,代碼如下:

class SingleDouble private constructor(){
    // synchronized
    @Volatile
    var singleton //	volatile修飾 線程同步
            : SingleDouble? = null
        get() {
            if (field == null) {
                synchronized(SingleDouble::class.java) {
                    if (field == null) {
                        field = SingleDouble()
                    }
                }
            }
            return field
        }
        private set

}

乍眼一看,沒啥問題,其實也沒啥問題,但是如果這樣寫,就真的太low了,我們引入Kotlin中獨特的懶加載,Lazy關鍵字。他屬於代理的一種模式,用於生命週期類中延遲初始化一些對象。比如我們現在需要第一次調用時候才實例,本身就屬於一個懶加載。

class SingleDouble private constructor(){
    companion object {
        val instance: SingleDouble by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
            SingleDouble() }
    }
}

這種方式是網上公推的,其實我也看得迷迷糊糊的,點到LazyThreadSafetyMode.SYNCHRONIZED裏面,其實說的很直白:鎖用於確保只有一個線程可以初始化[Lazy]實例。這句話基本上就概括了DCL中我們所想要的,有鎖,確保單個線程對實例的操作,延遲加載。

4. 靜態內部類

主要用它跟餓漢式對比,餓漢式只要類被加載,instance就會被實例化,而這種方式加載的時候instance 不一定被初始化。只有調用 getInstance 方法時,纔會顯式裝載 SingletonHolder 類,從而實例化 instance。

Java下實現

public class Single3 {
    private static class SingletonHolder {
        private static final Single3 INSTANCE = new Single3();
    }

    private Single3() {}

    public static final Single3 getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

Kotlin下實現

這個差異性就不大了,大家看代碼基本就能看個七七八八了。

class Single3 private constructor(){

    companion object {
        val instance = SingletonHolder.holder
    }

    private object SingletonHolder {
        val holder= Single3()
    }

}

5. 枚舉

我懶得寫,懂我的人一定知道我爲什麼懶得寫,因爲個人喜好,我特別討厭枚舉,除了看着爽一點(我看着也不爽),其實對性能沒有提升(甚至有一定的降低),當然這些都無關痛癢,主要原因就是我不喜歡。

2020年5月31日23:58:25 又到了這個點,明天就是 六一兒童節,過節過節~~~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章