kotlin 的靜態和單列模式

靜態參考:https://www.jianshu.com/p/14db81e1576a

https://www.jianshu.com/p/e8752c880088

單列模式參考:https://www.jianshu.com/p/5797b3d0ebd0

靜態

一、 object關鍵字
object 關鍵字可以表達兩種含義:一種是對象表達式,另一種是 對象聲明。

1、對象表達式
繼承一個匿名對象

val textView = findViewById(R.id.tv)
textView.setOnClickListener(object : OnClickListener {
override fun onClick(p0: View?) {
Toast.makeText(this@TestActivity, “點擊事件生效”, Toast.LENGTH_LONG)
}

})
上面代碼其實就是我們經常要給 view 設置的點擊事件,OnClickListener 事件是一個匿名類的對象,用object來修飾。

2、對象聲明
用object 修飾的類爲靜態類,裏面的方法和變量都爲靜態的。

2.1 直接聲明類
object DemoManager {
private val TAG = “DemoManager”

fun a() {
    Log.e(TAG,"此時 object 表示 聲明靜態內部類")
}

}
2.2 聲明靜態內部類
類內部的對象聲明,沒有被inner 修飾的內部類都是靜態的

class DemoManager{
object MyObject {
fun a() {
Log.e(TAG,“此時 object 表示 直接聲明類”)
}
}
}
如果需要調用 a()方法

kotlin中調用
fun init() {
MyObject.a()
}
java中調用
MyObject.INSTANCE.a();

二、companion object
companion object 修飾爲伴生對象,伴生對象在類中只能存在一個,類似於java中的靜態方法 Java 中使用類訪問靜態成員,靜態方法。

companion object {
private val TAG = “DemoManager”

fun b() {
    Log.e(TAG,"此時 companion objec t表示 伴生對象")
}

}
kotlin 中調用

fun init(){
b()
}
java 中調用

DemoManager.Companion.b();

companion object 相關的內容可以查閱 Kotlin中常量和靜態方法 這篇文章,在這裏不多在具體描述。

三、在companion object中如何調用外部的成員變量
3.1 爲什麼companion object 中調用不到外部成員變量
class DemoManager {
private val MY_TAG = “DemoManager”

fun init(){
   b()

}

companion object {
    fun b() {
        Log.e(MY_TAG,"此時 companion objec t表示 伴生對象")
    }
}

}
在上面代碼中MY_TAG 是不會被調用到的。

原理很簡單:

在java中我們寫一個靜態方法,如果需要調用成員變量,是無法調用到的

private String TAG = “MainActivity”;

public static void init(){
Log.e(TAG,"init() ");
}
只有將 TAG 修改爲靜態成員變量才能調用到

private static String TAG = “MainActivity”;

public static void init(){
Log.e(TAG,"init() ");
}
由此可以看出來,java中靜態方法調用成員變量,要求成員變量必須是靜態的, 在kotlin 中也是一樣,所以當companion object 中調用非靜態的成員變量也是調用不到的。

3.2 怎樣解決才能調用到呢?
companion object {
private val MY_TAG = “DemoManager”
fun b() {
Log.e(MY_TAG,“此時 companion objec t表示 伴生對象”)
}
}
PS:該篇文章不討論單例模式的運用場景與各種模式下的單例模式的優缺點。只討論在Java下不同單例模式下的對應Kotlin實現。

一、餓漢式實現
//Java實現
public class SingletonDemo {
private static SingletonDemo instance=new SingletonDemo();
private SingletonDemo(){

}
public static SingletonDemo getInstance(){
    return instance;
}

}
//Kotlin實現
object SingletonDemo
這裏很多小夥伴,就吃了一驚。我靠一個object 關鍵字就完成相同的功能?一行代碼?

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

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

查看Kotlin對應字節碼
查看Kotlin字節碼.png
我們進入我們的Android Studio(我的Android Studio 3.0,如果你的編譯器版本過低,請自動升級) 選擇Tools工具欄,選擇"Kotlin",選擇“Show Kotlin Bytecode"

選擇過後就會進入到下方界面:

查看Kotlin字節碼.png
點擊"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實現中,我們讓其主構造函數私有化並自定義了其屬性訪問器,其餘內容大同小異。

如果有小夥伴不清楚Kotlin構造函數的使用方式。請點擊 - - - 構造函數
不清楚Kotlin的屬性與訪問器,請點擊 - - -屬性和字段
三、線程安全的懶漢式
//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註解。

四、雙重校驗鎖式(Double Check)
//Java實現
public class SingletonDemo {
private volatile static SingletonDemo instance;
private SingletonDemo(){}
public static SingletonDemo getInstance(){
if(instancenull){
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的內部實現。

Lazy內部實現
public fun lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy =
when (mode) {
LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)
LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)
LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)
}
觀察上述代碼,因爲我們傳入的mode = LazyThreadSafetyMode.SYNCHRONIZED,
那麼會直接走 SynchronizedLazyImpl,我們繼續觀察SynchronizedLazyImpl。

Lazy接口
SynchronizedLazyImpl實現了Lazy接口,Lazy具體接口如下:

public interface Lazy {
//當前實例化對象,一旦實例化後,該對象不會再改變
public val value: T
//返回true表示,已經延遲實例化過了,false 表示,沒有被實例化,
//一旦方法返回true,該方法會一直返回true,且不會再繼續實例化
public fun isInitialized(): Boolean
}
繼續查看SynchronizedLazyImpl,具體實現如下:

SynchronizedLazyImpl內部實現
private class SynchronizedLazyImpl(initializer: () -> T, lock: Any? = null) : Lazy, 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 Lazy.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是一樣的。

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