最近在學習Kotlin這門語言,在項目開發中,運用到了單例模式。由於其表達方式與Java是不一樣的。因此對不一樣單例模式的實現進行了分別探討。主要單例模式實現以下:html
PS:該篇文章不討論單例模式的運用場景與各類模式下的單例模式的優缺點。只討論在Java下不一樣單例模式下的對應Kotlin實現。git
//Java實現 public class SingletonDemo { private static SingletonDemo instance=new SingletonDemo(); private SingletonDemo(){ } public static SingletonDemo getInstance(){ return instance; } } //Kotlin實現 object SingletonDemo 複製代碼
這裏不少小夥伴,就吃了一驚。我靠一個object 關鍵字就完成相同的功能?一行代碼?github
學習了Kotlin的小夥伴確定知道,在Kotlin中類沒有靜態方法。若是你須要寫一個能夠無需用一個類的實例來調用,但須要訪問類內部的函數(例如,工廠方法,單例等),你能夠把該類聲明爲一個對象。該對象與其餘語言的靜態成員是相似的。若是你想了解Kotlin對象聲明的更多內容。請點擊- - - 傳送門安全
到這裏,若是仍是有不少小夥伴不是很相信一行代碼就能解決這個功能,咱們能夠經過一下方式查看Kotlin的字節碼。bash
咱們進入咱們的Android Studio(個人Android Studio 3.0,若是你的編譯器版本太低,請自動升級) 選擇Tools工具欄,選擇"Kotlin",選擇「Show Kotlin Bytecode"markdown
選擇事後就會進入到下方界面: ide
點擊"Decompile" 根據字節碼獲得如下代碼函數
public final class SingletonDemo { public static final SingletonDemo INSTANCE; private SingletonDemo(){} static { SingletonDemo var0 = new SingletonDemo(); INSTANCE = var0; } } 複製代碼
經過以上代碼,咱們瞭解事實就是這個樣子的,使用Kotlin"object"進行對象聲明與咱們的餓漢式單例的代碼是相同的。工具
//Java實現 public class SingletonDemo { private static SingletonDemo instance; private SingletonDemo(){} public static SingletonDemo getInstance(){ if(instance==null){ instance=new SingletonDemo(); } return instance; } } //Kotlin實現 class SingletonDemo private constructor() { companion object { private var instance: SingletonDemo? = null get() { if (field == null) { field = SingletonDemo() } return field } fun get(): SingletonDemo{ //細心的小夥伴確定發現了,這裏不用getInstance做爲爲方法名,是由於在伴生對象聲明時,內部已有getInstance方法,因此只能取其餘名字 return instance!! } } } 複製代碼
上述代碼中,咱們能夠發如今Kotlin實現中,咱們讓其主構造函數私有化並自定義了其屬性訪問器,其他內容大同小異。oop
//Java實現 public class SingletonDemo { private static SingletonDemo instance; private SingletonDemo(){} public static synchronized SingletonDemo getInstance(){//使用同步鎖 if(instance==null){ instance=new SingletonDemo(); } return instance; } } //Kotlin實現 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!! } } } 複製代碼
你們都知道在使用懶漢式會出現線程安全的問題,須要使用使用同步鎖,在Kotlin中,若是你須要將方法聲明爲同步,須要添加**@Synchronized**註解。
//Java實現 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實現 class SingletonDemo private constructor() { companion object { val instance: SingletonDemo by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { SingletonDemo() } } } 複製代碼
哇!小夥伴們驚喜不,感不感動啊。咱們竟然幾行代碼就實現了多行的Java代碼。其中咱們運用到了Kotlin的延遲屬性 Lazy。
Lazy是接受一個 lambda 並返回一個 Lazy 實例的函數,返回的實例能夠做爲實現延遲屬性的委託: 第一次調用 get() 會執行已傳遞給 lazy() 的 lambda 表達式並記錄結果, 後續調用 get() 只是返回記錄的結果。
這裏還有有兩個額外的知識點。
若是你瞭解以上知識點,咱們直接來看Lazy的內部實現。
public fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =
when (mode) {
LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)
LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)
LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)
}
複製代碼
觀察上述代碼,由於咱們傳入的mode = LazyThreadSafetyMode.SYNCHRONIZED, 那麼會直接走 SynchronizedLazyImpl,咱們繼續觀察SynchronizedLazyImpl。
SynchronizedLazyImpl實現了Lazy接口,Lazy具體接口以下:
public interface Lazy<out T> { //當前實例化對象,一旦實例化後,該對象不會再改變 public val value: T //返回true表示,已經延遲實例化過了,false 表示,沒有被實例化, //一旦方法返回true,該方法會一直返回true,且不會再繼續實例化 public fun isInitialized(): Boolean } 複製代碼
繼續查看SynchronizedLazyImpl,具體實現以下:
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 //判斷是否已經初始化過,若是初始化過直接返回,不在調用高級函數內部邏輯 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!!()//調用高級函數獲取其返回值 _value = typedValue //將返回值賦值給_value,用於下次判斷時,直接返回高級函數的返回值 initializer = null typedValue } } } //省略部分代碼 } 複製代碼
經過上述代碼,咱們發現 SynchronizedLazyImpl 覆蓋了Lazy接口的value屬性,而且從新了其屬性訪問器。其具體邏輯與Java的雙重檢驗是相似的。
到裏這裏其實你們仍是確定有疑問,我這裏只是實例化了SynchronizedLazyImpl對象,並無進行值的獲取,它是怎麼拿到高階函數的返回值呢?。這裏又涉及到了委託屬性。
委託屬性語法是: val/var <屬性名>: <類型> by <表達式>。在 by 後面的表達式是該 委託, 由於屬性對應的 get()(和 set())會被委託給它的 getValue() 和 setValue() 方法。 屬性的委託沒必要實現任何的接口,可是須要提供一個 getValue() 函數(和 setValue()——對於 var 屬性)。
而Lazy.kt文件中,聲明瞭Lazy接口的getValue擴展函數。故在最終賦值的時候會調用該方法。
@kotlin.internal.InlineOnly
//返回初始化的值。
public inline operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value
複製代碼
//Java實現 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實現 class SingletonDemo private constructor() { companion object { val instance = SingletonHolder.holder } private object SingletonHolder { val holder= SingletonDemo() } } 複製代碼
靜態內部類的實現方式,也沒有什麼好說的。Kotlin與Java實現基本雷同。
在該篇文章結束後,有不少小夥伴諮詢,如何在Kotlin版的Double Check,給單例添加一個屬性,這裏我給你們提供了一個實現的方式。(很差意思,最近才抽出時間來解決這個問題)
class SingletonDemo private constructor(private val property: Int) {//這裏能夠根據實際需求發生改變
companion object {
@Volatile private var instance: SingletonDemo? = null
fun getInstance(property: Int) =
instance ?: synchronized(this) {
instance ?: SingletonDemo(property).also { instance = it }
}
}
}
複製代碼
其中關於?:
操做符,若是 ?:
左側表達式非空,就返回其左側表達式,不然返回右側表達式。 請注意,當且僅當左側爲空時,纔會對右側表達式求值。
觀察代碼咱們能夠發現大體上和咱們的Java中的Double check是同樣的。
附上我寫的一個基於Kotlin 仿開眼的項目SimpleEyes(ps: 其實在我以前,已經有不少小朋友開始仿這款應用了,可是我以爲要作就作好。因此個人項目和其餘的人應該不一樣,不只僅是簡單的一個應用。可是,可是。可是。重要的話說三遍。還在開發階段,不要打我),歡迎你們follow和start