Kotlin下的5種單例模式

Kotlin.jpg

前言

最近在學(xué)習(xí)Kotlin這門語言,在項(xiàng)目開發(fā)中,運(yùn)用到了單例模式。因?yàn)槠浔磉_(dá)方式與Java是不同的。所以對不同單例模式的實(shí)現(xiàn)進(jìn)行了分別探討。主要單例模式實(shí)現(xiàn)如下:

  • 餓漢式
  • 懶漢式
  • 線程安全的懶漢式
  • 雙重校驗(yàn)鎖式
  • 靜態(tài)內(nèi)部類式

PS:該篇文章不討論單例模式的運(yùn)用場景與各種模式下的單例模式的優(yōu)缺點(diǎn)。只討論在Java下不同單例模式下的對應(yīng)Kotlin實(shí)現(xiàn)。

一、餓漢式實(shí)現(xiàn)

//Java實(shí)現(xiàn)
public class SingletonDemo {
    private static SingletonDemo instance=new SingletonDemo();
    private SingletonDemo(){

    }
    public static SingletonDemo getInstance(){
        return instance;
    }
}
//Kotlin實(shí)現(xiàn)
object SingletonDemo

這里很多小伙伴,就吃了一驚。我靠一個(gè)object 關(guān)鍵字就完成相同的功能?一行代碼?

Kotlin的對象聲明

學(xué)習(xí)了Kotlin的小伙伴肯定知道,在Kotlin中類沒有靜態(tài)方法。如果你需要寫一個(gè)可以無需用一個(gè)類的實(shí)例來調(diào)用,但需要訪問類內(nèi)部的函數(shù)(例如,工廠方法,單例等),你可以把該類聲明為一個(gè)對象。該對象與其他語言的靜態(tài)成員是類似的。如果你想了解Kotlin對象聲明的更多內(nèi)容。請點(diǎn)擊- - - 傳送門

到這里,如果還是有很多小伙伴不是很相信一行代碼就能解決這個(gè)功能,我們可以通過一下方式查看Kotlin的字節(jié)碼。

查看Kotlin對應(yīng)字節(jié)碼
查看Kotlin字節(jié)碼.png

我們進(jìn)入我們的Android Studio(我的Android Studio 3.0,如果你的編譯器版本過低,請自動升級) 選擇Tools工具欄,選擇"Kotlin",選擇“Show Kotlin Bytecode"

選擇過后就會進(jìn)入到下方界面:


查看Kotlin字節(jié)碼.png

點(diǎn)擊"Decompile" 根據(jù)字節(jié)碼得到以下代碼

public final class SingletonDemo {
   public static final SingletonDemo INSTANCE;
   private SingletonDemo(){}
   static {
      SingletonDemo var0 = new SingletonDemo();
      INSTANCE = var0;
   }
}

通過以上代碼,我們了解事實(shí)就是這個(gè)樣子的,使用Kotlin"object"進(jìn)行對象聲明與我們的餓漢式單例的代碼是相同的。

二、懶漢式

//Java實(shí)現(xiàn)
public class SingletonDemo {
    private static SingletonDemo instance;
    private SingletonDemo(){}
    public static SingletonDemo getInstance(){
        if(instance==null){
            instance=new SingletonDemo();
        }
        return instance;
    }
}
//Kotlin實(shí)現(xiàn)
class SingletonDemo private constructor() {
    companion object {
        private var instance: SingletonDemo? = null
            get() {
                if (field == null) {
                    field = SingletonDemo()
                }
                return field
            }
        fun get(): SingletonDemo{
        //細(xì)心的小伙伴肯定發(fā)現(xiàn)了,這里不用getInstance作為為方法名,是因?yàn)樵诎樯鷮ο舐暶鲿r(shí),內(nèi)部已有g(shù)etInstance方法,所以只能取其他名字
         return instance!!
        }
    }
}

上述代碼中,我們可以發(fā)現(xiàn)在Kotlin實(shí)現(xiàn)中,我們讓其主構(gòu)造函數(shù)私有化并自定義了其屬性訪問器,其余內(nèi)容大同小異。

  • 如果有小伙伴不清楚Kotlin構(gòu)造函數(shù)的使用方式。請點(diǎn)擊 - - - 構(gòu)造函數(shù)
  • 不清楚Kotlin的屬性與訪問器,請點(diǎn)擊 - - -屬性和字段

三、線程安全的懶漢式

//Java實(shí)現(xiàn)
public class SingletonDemo {
    private static SingletonDemo instance;
    private SingletonDemo(){}
    public static synchronized SingletonDemo getInstance(){//使用同步鎖
        if(instance==null){
            instance=new SingletonDemo();
        }
        return instance;
    }
}
//Kotlin實(shí)現(xiàn)
class SingletonDemo private constructor() {
    companion object {
        private var instance: SingletonDemo? = null
            get() {
                if (field == null) {
                    field = SingletonDemo()
                }
                return field
            }
        @Synchronized
        fun get(): SingletonDemo{
            return instance!!
        }
    }

}

大家都知道在使用懶漢式會出現(xiàn)線程安全的問題,需要使用使用同步鎖,在Kotlin中,如果你需要將方法聲明為同步,需要添加@Synchronized注解。

四、雙重校驗(yàn)鎖式(Double Check)

//Java實(shí)現(xiàn)
public class SingletonDemo {
    private volatile static SingletonDemo instance;
    private SingletonDemo(){} 
    public static SingletonDemo getInstance(){
        if(instance==null){
            synchronized (SingletonDemo.class){
                if(instance==null){
                    instance=new SingletonDemo();
                }
            }
        }
        return instance;
    }
}
//kotlin實(shí)現(xiàn)
class SingletonDemo private constructor() {
    companion object {
        val instance: SingletonDemo by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
        SingletonDemo() }
    }
}

哇!小伙伴們驚喜不,感不感動啊。我們居然幾行代碼就實(shí)現(xiàn)了多行的Java代碼。其中我們運(yùn)用到了Kotlin的延遲屬性 Lazy。

Lazy是接受一個(gè) lambda 并返回一個(gè) Lazy 實(shí)例的函數(shù),返回的實(shí)例可以作為實(shí)現(xiàn)延遲屬性的委托: 第一次調(diào)用 get() 會執(zhí)行已傳遞給 lazy() 的 lambda 表達(dá)式并記錄結(jié)果, 后續(xù)調(diào)用 get() 只是返回記錄的結(jié)果。

這里還有有兩個(gè)額外的知識點(diǎn)。

  • 高階函數(shù),高階函數(shù)是將函數(shù)用作參數(shù)或返回值的函數(shù)(我很糾結(jié)我到底講不講,哎)。大家還是看這個(gè) ---高階函數(shù)
  • 委托屬性

如果你了解以上知識點(diǎn),我們直接來看Lazy的內(nèi)部實(shí)現(xiàn)。

Lazy內(nèi)部實(shí)現(xiàn)
public fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =
        when (mode) {
            LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)
            LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)
            LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)
        }

觀察上述代碼,因?yàn)槲覀儌魅氲?strong>mode = LazyThreadSafetyMode.SYNCHRONIZED,
那么會直接走 SynchronizedLazyImpl,我們繼續(xù)觀察SynchronizedLazyImpl。

Lazy接口

SynchronizedLazyImpl實(shí)現(xiàn)了Lazy接口,Lazy具體接口如下:

public interface Lazy<out T> {
     //當(dāng)前實(shí)例化對象,一旦實(shí)例化后,該對象不會再改變
    public val value: T
    //返回true表示,已經(jīng)延遲實(shí)例化過了,false 表示,沒有被實(shí)例化,
    //一旦方法返回true,該方法會一直返回true,且不會再繼續(xù)實(shí)例化
    public fun isInitialized(): Boolean
}

繼續(xù)查看SynchronizedLazyImpl,具體實(shí)現(xiàn)如下:

SynchronizedLazyImpl內(nèi)部實(shí)現(xiàn)
private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
    private var initializer: (() -> T)? = initializer
    @Volatile private var _value: Any? = UNINITIALIZED_VALUE
    // final field is required to enable safe publication of constructed instance
    private val lock = lock ?: this

    override val value: T
        get() {
            val _v1 = _value
            //判斷是否已經(jīng)初始化過,如果初始化過直接返回,不在調(diào)用高級函數(shù)內(nèi)部邏輯
            if (_v1 !== UNINITIALIZED_VALUE) {
                @Suppress("UNCHECKED_CAST")
                return _v1 as T
            }

            return synchronized(lock) {
                val _v2 = _value
                if (_v2 !== UNINITIALIZED_VALUE) {
                    @Suppress("UNCHECKED_CAST") (_v2 as T)
                }
                else {
                    val typedValue = initializer!!()//調(diào)用高級函數(shù)獲取其返回值
                    _value = typedValue   //將返回值賦值給_value,用于下次判斷時(shí),直接返回高級函數(shù)的返回值
                    initializer = null
                    typedValue  
                }
            }
        }
        //省略部分代碼
}

通過上述代碼,我們發(fā)現(xiàn) SynchronizedLazyImpl 覆蓋了Lazy接口的value屬性,并且重新了其屬性訪問器。其具體邏輯與Java的雙重檢驗(yàn)是類似的。

到里這里其實(shí)大家還是肯定有疑問,我這里只是實(shí)例化了SynchronizedLazyImpl對象,并沒有進(jìn)行值的獲取,它是怎么拿到高階函數(shù)的返回值呢?。這里又涉及到了委托屬性。

委托屬性語法是: val/var <屬性名>: <類型> by <表達(dá)式>。在 by 后面的表達(dá)式是該 委托, 因?yàn)閷傩詫?yīng)的 get()(和 set())會被委托給它的 getValue() 和 setValue() 方法。 屬性的委托不必實(shí)現(xiàn)任何的接口,但是需要提供一個(gè) getValue() 函數(shù)(和 setValue()——對于 var 屬性)。

而Lazy.kt文件中,聲明了Lazy接口的getValue擴(kuò)展函數(shù)。故在最終賦值的時(shí)候會調(diào)用該方法。

@kotlin.internal.InlineOnly
//返回初始化的值。
public inline operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value

五、靜態(tài)內(nèi)部類式

//Java實(shí)現(xiàn)
public class SingletonDemo {
    private static class SingletonHolder{
        private static SingletonDemo instance=new SingletonDemo();
    }
    private SingletonDemo(){
        System.out.println("Singleton has loaded");
    }
    public static SingletonDemo getInstance(){
        return SingletonHolder.instance;
    }
}
//kotlin實(shí)現(xiàn)
class SingletonDemo private constructor() {
    companion object {
        val instance = SingletonHolder.holder
    }

    private object SingletonHolder {
        val holder= SingletonDemo()
    }

}

靜態(tài)內(nèi)部類的實(shí)現(xiàn)方式,也沒有什么好說的。Kotlin與Java實(shí)現(xiàn)基本雷同。

補(bǔ)充

在該篇文章結(jié)束后,有很多小伙伴咨詢,如何在Kotlin版的Double Check,給單例添加一個(gè)屬性,這里我給大家提供了一個(gè)實(shí)現(xiàn)的方式。(不好意思,最近才抽出時(shí)間來解決這個(gè)問題)

class SingletonDemo private constructor(private val property: Int) {//這里可以根據(jù)實(shí)際需求發(fā)生改變
  
    companion object {
        @Volatile private var instance: SingletonDemo? = null
        fun getInstance(property: Int) =
                instance ?: synchronized(this) {
                    instance ?: SingletonDemo(property).also { instance = it }
                }
    }
}

其中關(guān)于?:操作符,如果 ?: 左側(cè)表達(dá)式非空,就返回其左側(cè)表達(dá)式,否則返回右側(cè)表達(dá)式。 請注意,當(dāng)且僅當(dāng)左側(cè)為空時(shí),才會對右側(cè)表達(dá)式求值。

觀察代碼我們可以發(fā)現(xiàn)大致上和我們的Java中的Double check是一樣的。

最后

附上我寫的一個(gè)基于Kotlin 仿開眼的項(xiàng)目SimpleEyes(ps: 其實(shí)在我之前,已經(jīng)有很多小朋友開始仿這款應(yīng)用了,但是我覺得要做就做好。所以我的項(xiàng)目和其他的人應(yīng)該不同,不僅僅是簡單的一個(gè)應(yīng)用。但是,但是。但是。重要的話說三遍。還在開發(fā)階段,不要打我),歡迎大家follow和start

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容