kotlin object關(guān)鍵字單例模式實現(xiàn)示例詳解
正文
object
關(guān)鍵字有三種不同的語義:匿名內(nèi)部類、伴生對象、單例模式。因為 Kotlin 的設(shè)計者認(rèn)為,這三種語義本質(zhì)上都是在定義一個類的同時還創(chuàng)建了對象。在這樣的情況下,與其分別定義三種不同的關(guān)鍵字,還不如將它們統(tǒng)一成 object
關(guān)鍵字。
一、 匿名內(nèi)部類
Android中用java寫View的點擊事件:
findViewById(R.id.tv).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //do something } });
在 Kotlin 當(dāng)中,我們會使用 object 關(guān)鍵字來創(chuàng)建匿名內(nèi)部類。同樣,在它的內(nèi)部,我們也必須要實現(xiàn)它內(nèi)部未實現(xiàn)的方法。這種方式不僅可以用于創(chuàng)建接口的匿名內(nèi)部類,也可以創(chuàng)建抽象類的匿名內(nèi)部類:
findViewById<TextView>(R.id.tv).setOnClickListener(object : View.OnClickListener { override fun onClick(v: View?) { //do something } }) //上面的代碼可以用SAM轉(zhuǎn)換簡化,IDE會提示
Java 和 Kotlin 相同的地方就在于,它們的接口與抽象類,都不能直接創(chuàng)建實例。想要創(chuàng)建接口和抽象類的實例,我們必須通過匿名內(nèi)部類的方式。
在 Kotlin 中,匿名內(nèi)部類還有一個特殊之處,就是我們在使用 object 定義匿名內(nèi)部類的時候,其實還可以在繼承一個抽象類的同時,來實現(xiàn)多個接口:
//抽象類和抽象方法 abstract class Person{ abstract fun isAdult() } //接口 interface AListener { fun getA() } //接口 interface BListener { fun getB() } //繼承一個抽象類的同時,來實現(xiàn)多個接口 private val item = object :Person(),AListener,BListener{ override fun isAdult() { //do something } override fun getA() { //do something } override fun getB() { //do something } }
在日常的開發(fā)工作當(dāng)中,我們有時會遇到這種情況:我們需要繼承某個類,同時還要實現(xiàn)某些接口,為了達到這個目的,我們不得不定義一個內(nèi)部類,然后給它取個名字。但這樣的類,往往只會被用一次就再也沒有其他作用了。所以針對這種情況,使用 object 的這種語法就正好合適。我們既不用再定義內(nèi)部類,也不用想著該怎么給這個類取名字,因為用過一次后就不用再管了。
引申:可以把函數(shù)當(dāng)做參數(shù)簡化定義接口的操作。以前寫java時應(yīng)該都寫過很多如下的接口回調(diào):
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) } }
通過函數(shù)當(dāng)做參數(shù)就不需要定義接口了:
class DownloadFile { private var onSuccess: ((String?) -> Unit)? = null fun downloadFile(token:String) { val filePath = "" onSuccess?.invoke(filePath) } fun setOnDownloadResultListener(method:((String?) -> Unit)? = null){ this.onSuccess = method } } //調(diào)用 DownloadFile().downloadFile("") DownloadFile().setOnDownloadResultListener { filePath -> print("$filePath") }
二、單例模式
在 Kotlin 當(dāng)中,要實現(xiàn)單例模式其實非常簡單,我們直接用 object 修飾類即可:
object StringUtils { fun getLength(text: String?): Int = text?.length ?: 0 } //反編譯 public final class StringUtils { @NotNull public static final StringUtils INSTANCE; //靜態(tài)單例對象 public final int getLength(@Nullable String text) { return text != null ? text.length() : 0; } private StringUtils() { } static { //靜態(tài)代碼塊 StringUtils var0 = new StringUtils(); INSTANCE = var0; } }
這種方式定義的單例模式,雖然簡潔,但存在兩個缺點:
1、不支持懶加載。
2、不支持傳參構(gòu)造單例。寫構(gòu)造方法會報錯,會提示object修飾的類不允許有構(gòu)造方法。
三、伴生對象
1、深入分析伴生對象
Kotlin 當(dāng)中沒有 static 關(guān)鍵字,所以我們沒有辦法直接定義靜態(tài)方法和靜態(tài)變量。不過,Kotlin 還是為我們提供了伴生對象,來幫助實現(xiàn)靜態(tài)方法和變量。
我們先來看看 object 定義單例的一種特殊情況,看看它是如何演變成“伴生對象”的:
class User() { object InnerClass { fun foo() {} } }
用object修飾嵌套類,看下反編譯的結(jié)果:
public final class User { //object修飾的內(nèi)部類為靜態(tài)內(nèi)部類 public static final class Inner { @NotNull public static final User.Inner INSTANCE; //靜態(tài)單例對象 public final void foo() { } private Inner() { } //通過static靜態(tài)代碼塊創(chuàng)建了單例對象 static { User.Inner var0 = new User.Inner(); INSTANCE = var0; } } }
調(diào)用的時候的代碼
User.InnerClass.foo()
可以看到foo
方法并不是靜態(tài)方法,那加上@JvmStatic
這個注解試試:
class User() { object InnerClass { @JvmStatic fun foo() {} } } //反編譯結(jié)果 public final class User { public static final class InnerClass { @NotNull public static final User.InnerClass INSTANCE; @JvmStatic public static final void foo() { //foo方法變成了靜態(tài)方法 } private InnerClass() { } static { User.InnerClass var0 = new User.InnerClass(); INSTANCE = var0; } } }
foo
方法變成了一個靜態(tài)方法,但是在使用的時候還是要User.InnerClass.foo()
,而User類中的靜態(tài)方法應(yīng)該是直接User.foo()
調(diào)用才對,這還是不符合定義靜態(tài)方法的初衷。那在 Kotlin 如何實現(xiàn)這樣的靜態(tài)方法呢?我們只需要在前面例子當(dāng)中的 object 關(guān)鍵字前面,加一個 companion 關(guān)鍵字即可。
①不加@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(); } } } //調(diào)用 User.foo() //反編譯調(diào)用的代碼 User.InnerClass.foo();
如果不加上@JvmStatic
注解調(diào)用的時候只是省略了前面的單例對象InnerClass
,foo
仍然不是User
的靜態(tài)方法。
②加@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方法,但其實還是調(diào)用的下面的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(); } } }
可以看到這個時候多生成了一個靜態(tài)的foo
方法,可以通過User.foo()
真正去調(diào)用了,而不是省略掉了InnerClass
單例對象(把InnerClass
對象放在了靜態(tài)方法的實現(xiàn)中)。
那又有問題來了,上面二種方式應(yīng)該如何選擇,哪種情況下哪個好,什么時候該加注解什么時候不該加注解?
解析:1、用companion
修飾的對象會創(chuàng)建一個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中調(diào)用 User.Companion.foo();
如果不加@JvmStatic
,java調(diào)用kotlin代碼會多創(chuàng)建這個Companion
實例,會多一部分內(nèi)存開銷,所以如果這個靜態(tài)方法java需要調(diào)用,那務(wù)必要把@JvmStatic
加上。
2、多創(chuàng)建一個靜態(tài)foo
方法會不會多內(nèi)存開銷? 答案是不會,因為這個靜態(tài)的foo
方法調(diào)用的也是Companion
中的方法foo
方法,所以不會有多的內(nèi)存開銷。
2、用伴生對象實現(xiàn)工廠模式
所謂的工廠模式,就是指當(dāng)我們想要統(tǒng)一管理一個類的創(chuàng)建時,我們可以將這個類的構(gòu)造函數(shù)聲明成 private,然后用工廠模式來暴露一個統(tǒng)一的方法,以供外部使用。Kotlin 的伴生對象非常符合這樣的使用場景:
// 私有的構(gòu)造函數(shù),外部無法調(diào)用 class User private constructor(name: String) { companion object { @JvmStatic fun create(name: String): User? { // 統(tǒng)一檢查,比如敏感詞過濾 return User(name) } } }
3、用伴生對象實現(xiàn)單例模式
(1)、借助懶加載委托
class MainActivity : AppCompatActivity() { //借助懶加載委托實現(xiàn)單例 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() { //構(gòu)造方法 this.people$delegate = LazyKt.lazy((Function0)null.INSTANCE); //lazy方法有線程安全的實現(xiàn) } }
在MainActivity
的構(gòu)造方法中通過LazyKt.lazy
獲取類的代理對象,看下LazyKt.lazy
的源碼實現(xiàn):
/** * 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")
我們定義了一個伴生對象,然后在它的內(nèi)部,定義了一個 INSTANCE
,它是 private
的,這樣就保證了它無法直接被外部訪問。同時它還被注解“@Volatile
”修飾了,這可以保證INSTANCE
的可見性,而getInstance()
方法當(dāng)中的synchronized
,保證了INSTANCE
的原子性。因此,這種方案還是線程安全的。
同時,我們也能注意到,初始化情況下,INSTANCE
是等于 null
的。這也就意味著,只有在getInstance()
方法被使用的情況下,我們才會真正去加載用戶數(shù)據(jù)。這樣,我們就實現(xiàn)了整個UserManager
的懶加載,而不是它內(nèi)部的某個參數(shù)的懶加載。
另外,由于我們可以在調(diào)用getInstance(name)
方法的時候傳入初始化參數(shù),因此,這種方案也是支持傳參的。
單例模式最多的寫法,注意如果參數(shù)是上下文,不能傳遞Activity
或Fragment
的上下文,不然會有內(nèi)存泄漏。(單例的內(nèi)存泄漏)
(3)、抽象類模板
如果有多個類似于上面的單例,那么就會有很多重復(fù)代碼,于是嘗試抽象成模板代碼:
//要實現(xiàn)單例類,就只需要繼承這個 BaseSingleton 即可 //P為參數(shù),T為返回值 abstract class BaseSingleton<in P, out T> { @Volatile private var instance: T? = null //抽象方法,需要我們在具體的單例子類當(dāng)中實現(xiàn)此方法 protected abstract fun creator(param: P): T fun getInstance(param: P): T = instance ?: synchronized(this) { instance ?: creator(param).also { instance = it } } }
通過伴生對象實現(xiàn)抽象類,并給出具體實現(xiàn)
//構(gòu)建UploadFileManager對象需要一個帶參數(shù)的構(gòu)造方法 class UploadFileManager(val param: String) { //伴生對象實現(xiàn)BaseSingleton抽象類 companion object : BaseSingleton<String, UploadFileManager>() { //重寫方法并給出具體實現(xiàn) override fun creator(param: String): UploadFileManager { return UploadFileManager(param) } } fun foo(){ print("foo") } } //調(diào)用 UploadFileManager.getInstance("張三").foo()
因為構(gòu)造方法的限制這種封裝也有一定的局限性。
以上就是kotlin object關(guān)鍵字單例模式實現(xiàn)示例詳解的詳細內(nèi)容,更多關(guān)于kotlin object關(guān)鍵字單例模式的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android中EditText的drawableRight屬性設(shè)置點擊事件
這篇文章主要介紹了Android中EditText的drawableRight屬性的圖片設(shè)置點擊事件,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-10-10android通過Location API顯示地址信息的實現(xiàn)方法
這篇文章主要介紹了android通過Location API顯示地址信息的方法,涉及Android操作Geocoder類獲取地址信息的相關(guān)技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-07-07