網站首頁 編程語言 正文
正文
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?) -> Unit)? = null fun downloadFile(token:String) { val filePath = "" onSuccess?.invoke(filePath) } fun setOnDownloadResultListener(method:((String?) -> Unit)? = null){ this.onSuccess = method } } //調用 DownloadFile().downloadFile("") DownloadFile().setOnDownloadResultListener { filePath -> 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
注解調用的時候只是省略了前面的單例對象InnerClass
,foo
仍然不是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)
方法的時候傳入初始化參數,因此,這種方案也是支持傳參的。
單例模式最多的寫法,注意如果參數是上下文,不能傳遞Activity
或Fragment
的上下文,不然會有內存泄漏。(單例的內存泄漏)
(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
相關推薦
- 2022-07-02 C#并行編程之Task同步機制_C#教程
- 2022-07-16 idea 編寫springmvc項目并部署到Tomcat
- 2022-07-01 python神經網絡Batch?Normalization底層原理詳解_python
- 2023-01-30 Android自定義View模仿即刻點贊數字切換效果實例_Android
- 2023-07-25 npm login 時報錯npm ERR! code E403
- 2023-03-11 Pandas條件篩選與組合篩選的使用_python
- 2021-11-21 ASP.NET?Core應用JWT進行用戶認證及Token的刷新方案_實用技巧
- 2022-07-31 Jenkins定時構建語法規則及時間設置_相關技巧
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支