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翻譯過去的,流程入下圖。
首先我們注意Kotlin其中幾個有意思的特性。
- 變量會自動創建並隱藏get/set方法,這裏巧妙運用了這點,在get方法中判斷對象是否存在並獲取。
- Kt中,object 關鍵字聲明,其內部不允許聲明構造方法,使用有點類似匿名內部類的使用,所以大家一般也是用object來聲明單例。
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
得到字節碼如下:
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()
方法,雖然無傷大雅,但是憑着精益求精的原則,我們試着幹掉他。理下思路:
- 既然我們的
get()
是多餘的,我們就幹掉他。 - 字節碼中
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,也叫雙檢鎖/雙重校驗鎖倆個關鍵點:
volatile
關鍵字。簡單說某個線程改變了其所修飾的變量,立馬在內存中刷新,其他線程同步可知。- 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 又到了這個點,明天就是 六一兒童節,過節過節~~~