kotlin object关键字单例模式实现示例详解
作者:TimeFine
正文
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()
因为构造方法的限制这种封装也有一定的局限性。
以上就是kotlin object关键字单例模式实现示例详解的详细内容,更多关于kotlin object关键字单例模式的资料请关注脚本之家其它相关文章!