日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

kotlin?object關鍵字單例模式實現示例詳解_Android

作者:TimeFine ? 更新時間: 2023-03-01 編程語言

正文

object 關鍵字有三種不同的語義:匿名內部類、伴生對象、單例模式。因為 Kotlin 的設計者認為,這三種語義本質上都是在定義一個類的同時還創建了對象。在這樣的情況下,與其分別定義三種不同的關鍵字,還不如將它們統一成 object 關鍵字。

一、 匿名內部類

Android中用java寫View的點擊事件:

findViewById(R.id.tv).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        //do something
    }
});

在 Kotlin 當中,我們會使用 object 關鍵字來創建匿名內部類。同樣,在它的內部,我們也必須要實現它內部未實現的方法。這種方式不僅可以用于創建接口的匿名內部類,也可以創建抽象類的匿名內部類:

findViewById<TextView>(R.id.tv).setOnClickListener(object : View.OnClickListener {
    override fun onClick(v: View?) {
        //do something
    }
})
//上面的代碼可以用SAM轉換簡化,IDE會提示

Java 和 Kotlin 相同的地方就在于,它們的接口與抽象類,都不能直接創建實例。想要創建接口和抽象類的實例,我們必須通過匿名內部類的方式。

在 Kotlin 中,匿名內部類還有一個特殊之處,就是我們在使用 object 定義匿名內部類的時候,其實還可以在繼承一個抽象類的同時,來實現多個接口:

//抽象類和抽象方法
 abstract class Person{
     abstract  fun isAdult()
}
//接口
interface AListener {
    fun getA()
}
//接口
interface BListener {
    fun getB()
}
//繼承一個抽象類的同時,來實現多個接口
private val  item = object :Person(),AListener,BListener{
    override fun isAdult() {
        //do something
    }
    override fun getA() {
        //do something
    }
    override fun getB() {
        //do something
    }
}

在日常的開發工作當中,我們有時會遇到這種情況:我們需要繼承某個類,同時還要實現某些接口,為了達到這個目的,我們不得不定義一個內部類,然后給它取個名字。但這樣的類,往往只會被用一次就再也沒有其他作用了。所以針對這種情況,使用 object 的這種語法就正好合適。我們既不用再定義內部類,也不用想著該怎么給這個類取名字,因為用過一次后就不用再管了。

引申:可以把函數當做參數簡化定義接口的操作。以前寫java時應該都寫過很多如下的接口回調:

class DownloadFile {
    //攜帶token下載文件
    fun downloadFile(token:String) {
        val filePath = ""
        listener?.onSuccess(filePath)
    }
    //定義成員變量
    private var listener: OnDownloadResultListener? = null
    //寫set方法
    fun setOnDownloadResultListener(listener: OnDownloadResultListener){
        this.listener = listener
    }
    //定義接口
    interface OnDownloadResultListener {
        fun onSuccess(filePath:String)
    }
}

通過函數當做參數就不需要定義接口了:

class DownloadFile {
    private var onSuccess: ((String?) -&gt; Unit)? = null
    fun downloadFile(token:String) {
        val filePath = ""
        onSuccess?.invoke(filePath)
    }
    fun setOnDownloadResultListener(method:((String?) -&gt; Unit)? = null){
        this.onSuccess = method
    }
}
//調用
DownloadFile().downloadFile("")
DownloadFile().setOnDownloadResultListener { filePath -&gt;
    print("$filePath")
}

二、單例模式

在 Kotlin 當中,要實現單例模式其實非常簡單,我們直接用 object 修飾類即可:

object StringUtils {
    fun getLength(text: String?): Int = text?.length ?: 0
}
//反編譯
public final class StringUtils {
   @NotNull
   public static final StringUtils INSTANCE;  //靜態單例對象
   public final int getLength(@Nullable String text) {
      return text != null ? text.length() : 0;
   }
   private StringUtils() {
   }
   static {  //靜態代碼塊
      StringUtils var0 = new StringUtils();
      INSTANCE = var0;
   }
}

這種方式定義的單例模式,雖然簡潔,但存在兩個缺點:

1、不支持懶加載。

2、不支持傳參構造單例。寫構造方法會報錯,會提示object修飾的類不允許有構造方法。

三、伴生對象

1、深入分析伴生對象

Kotlin 當中沒有 static 關鍵字,所以我們沒有辦法直接定義靜態方法和靜態變量。不過,Kotlin 還是為我們提供了伴生對象,來幫助實現靜態方法和變量。

我們先來看看 object 定義單例的一種特殊情況,看看它是如何演變成“伴生對象”的:

class User() {
    object InnerClass {
        fun foo() {}
    }
}

用object修飾嵌套類,看下反編譯的結果:

public final class User {
   //object修飾的內部類為靜態內部類
   public static final class Inner {
      @NotNull
      public static final User.Inner INSTANCE;  //靜態單例對象
      public final void foo() {
      }
      private Inner() {
      }
      //通過static靜態代碼塊創建了單例對象
      static {
         User.Inner var0 = new User.Inner();
         INSTANCE = var0;
      }
   }
}

調用的時候的代碼

User.InnerClass.foo()

可以看到foo方法并不是靜態方法,那加上@JvmStatic這個注解試試:

class User() {
    object InnerClass {
        @JvmStatic
        fun foo() {}
    }
}
//反編譯結果
public final class User {
   public static final class InnerClass {
      @NotNull
      public static final User.InnerClass INSTANCE;
      @JvmStatic
      public static final void foo() {   //foo方法變成了靜態方法
      }
      private InnerClass() {
      }
      static {
         User.InnerClass var0 = new User.InnerClass();
         INSTANCE = var0;
      }
   }
}

foo方法變成了一個靜態方法,但是在使用的時候還是要User.InnerClass.foo(),而User類中的靜態方法應該是直接User.foo()調用才對,這還是不符合定義靜態方法的初衷。那在 Kotlin 如何實現這樣的靜態方法呢?我們只需要在前面例子當中的 object 關鍵字前面,加一個 companion 關鍵字即可。

①不加@JvmStatic注解

//假如不加@JvmStatic注解
class User() {
   companion object InnerClass {
        fun foo() {}
    }
}
//反編譯
public final class User {
   @NotNull
   public static final User.InnerClass InnerClass = new User.InnerClass((DefaultConstructorMarker)null);
   public static final class InnerClass {
      public final void foo() {
      }
      private InnerClass() {
      }
      // $FF: synthetic method
      public InnerClass(DefaultConstructorMarker $constructor_marker) {
         this();
      }
   }
}
//調用
User.foo()
//反編譯調用的代碼
User.InnerClass.foo();

如果不加上@JvmStatic注解調用的時候只是省略了前面的單例對象InnerClassfoo仍然不是User的靜態方法。

②加@JvmStatic注解

//假如加@JvmStatic注解
class User() {
   companion object InnerClass {
       @JvmStatic
        fun foo() {}
    }
}
//反編譯
public final class User {
   @NotNull
   public static final User.InnerClass InnerClass = new User.InnerClass((DefaultConstructorMarker)null);
   @JvmStatic
   public static final void foo() {  //多生成了一個foo方法,但其實還是調用的下面的foo方法
      InnerClass.foo();
   }
   public static final class InnerClass {
      @JvmStatic
      public final void foo() {   //實際的foo方法
      }
      private InnerClass() {
      }
      // $FF: synthetic method
      public InnerClass(DefaultConstructorMarker $constructor_marker) {
         this();
      }
   }
}

可以看到這個時候多生成了一個靜態的foo方法,可以通過User.foo()真正去調用了,而不是省略掉了InnerClass單例對象(把InnerClass對象放在了靜態方法的實現中)。

那又有問題來了,上面二種方式應該如何選擇,哪種情況下哪個好,什么時候該加注解什么時候不該加注解?

解析:1、用companion修飾的對象會創建一個Companion的實例:

class User {
   companion object {
        fun foo() {}
    }
}
//反編譯
public final class User {
   @NotNull
   public static final User.Companion Companion = new User.Companion((DefaultConstructorMarker)null);
   public static final class Companion {
      public final void foo() {
      }
      private Companion() {
      }
      // $FF: synthetic method
      public Companion(DefaultConstructorMarker $constructor_marker) {
         this();
      }
   }
}
//java中調用
User.Companion.foo();

如果不加@JvmStatic,java調用kotlin代碼會多創建這個Companion實例,會多一部分內存開銷,所以如果這個靜態方法java需要調用,那務必要把@JvmStatic加上。

2、多創建一個靜態foo方法會不會多內存開銷? 答案是不會,因為這個靜態的foo方法調用的也是Companion中的方法foo方法,所以不會有多的內存開銷。

2、用伴生對象實現工廠模式

所謂的工廠模式,就是指當我們想要統一管理一個類的創建時,我們可以將這個類的構造函數聲明成 private,然后用工廠模式來暴露一個統一的方法,以供外部使用。Kotlin 的伴生對象非常符合這樣的使用場景:

// 私有的構造函數,外部無法調用
class User private constructor(name: String) {
    companion object {
     @JvmStatic
         fun create(name: String): User? {
            // 統一檢查,比如敏感詞過濾
            return User(name)
        }
    }
}

3、用伴生對象實現單例模式

(1)、借助懶加載委托

class MainActivity : AppCompatActivity() {
    //借助懶加載委托實現單例
    private val people by lazy { People("張三", 18) }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}
//反編譯后
public final class MainActivity extends AppCompatActivity {
   private final Lazy people$delegate;
   private final People getPeople() {
      Lazy var1 = this.people$delegate;
      Object var3 = null;
      return (People)var1.getValue();
   }
   protected void onCreate(@Nullable Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      this.setContentView(1300000);
   }
   public MainActivity() {   //構造方法
      this.people$delegate = LazyKt.lazy((Function0)null.INSTANCE);  //lazy方法有線程安全的實現
   }
}

MainActivity的構造方法中通過LazyKt.lazy獲取類的代理對象,看下LazyKt.lazy的源碼實現:

/**
 * Creates a new instance of the [Lazy] that uses the specified initialization function [initializer]
 * and the default thread-safety mode [LazyThreadSafetyMode.SYNCHRONIZED].   //線程安全模式
 *
 * If the initialization of a value throws an exception, it will attempt to reinitialize the value at next access.
 *
 * Note that the returned instance uses itself to synchronize on. Do not synchronize from external code on
 * the returned instance as it may cause accidental deadlock. Also this behavior can be changed in the future.
 */
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
/**
 * Creates a new instance of the [Lazy] that uses the specified initialization function [initializer]
 * and thread-safety [mode].
 *
 * If the initialization of a value throws an exception, it will attempt to reinitialize the value at next access.
 *
 * Note that when the [LazyThreadSafetyMode.SYNCHRONIZED] mode is specified the returned instance uses itself
 * to synchronize on. Do not synchronize from external code on the returned instance as it may cause accidental deadlock.
 * Also this behavior can be changed in the future.
 */
public actual fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =
    when (mode) {
        LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)
        LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)
        LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)
    }

(2)、伴生對象 Double Check

class UserManager private constructor(name: String) {
    companion object {
        @Volatile 
        private var INSTANCE: UserManager? = null
        fun getInstance(name: String): UserManager =
            // 第一次判空
            INSTANCE?: synchronized(this) {
                // 第二次判空
                INSTANCE?:UserManager(name).also { INSTANCE = it }
            }
     }
}
// 使用
UserManager.getInstance("Tom")

我們定義了一個伴生對象,然后在它的內部,定義了一個 INSTANCE,它是 private的,這樣就保證了它無法直接被外部訪問。同時它還被注解“@Volatile”修飾了,這可以保證INSTANCE的可見性,而getInstance()方法當中的synchronized,保證了INSTANCE的原子性。因此,這種方案還是線程安全的。

同時,我們也能注意到,初始化情況下,INSTANCE 是等于 null 的。這也就意味著,只有在getInstance() 方法被使用的情況下,我們才會真正去加載用戶數據。這樣,我們就實現了整個UserManager的懶加載,而不是它內部的某個參數的懶加載。

另外,由于我們可以在調用getInstance(name) 方法的時候傳入初始化參數,因此,這種方案也是支持傳參的。

單例模式最多的寫法,注意如果參數是上下文,不能傳遞ActivityFragment的上下文,不然會有內存泄漏。(單例的內存泄漏)

(3)、抽象類模板

如果有多個類似于上面的單例,那么就會有很多重復代碼,于是嘗試抽象成模板代碼:

//要實現單例類,就只需要繼承這個 BaseSingleton 即可
//P為參數,T為返回值
abstract class BaseSingleton<in P, out T> {
    @Volatile
    private var instance: T? = null
    //抽象方法,需要我們在具體的單例子類當中實現此方法
    protected abstract fun creator(param: P): T
    fun getInstance(param: P): T =
        instance ?: synchronized(this) {
            instance ?: creator(param).also { instance = it }
        }
}

通過伴生對象實現抽象類,并給出具體實現

//構建UploadFileManager對象需要一個帶參數的構造方法
class UploadFileManager(val param: String) {
    //伴生對象實現BaseSingleton抽象類
    companion object : BaseSingleton<String, UploadFileManager>() {
        //重寫方法并給出具體實現
        override fun creator(param: String): UploadFileManager {
            return UploadFileManager(param)
        }
    }
    fun foo(){
        print("foo")
    }
}
//調用
UploadFileManager.getInstance("張三").foo()

因為構造方法的限制這種封裝也有一定的局限性。

原文鏈接:https://juejin.cn/post/7186854600257830970

欄目分類
最近更新