Kotlin中的5种单例模式示例详解

所属分类: 软件编程 / Android 阅读数: 40
收藏 0 赞 0 分享

前言

最近在学习Kotlin这门语言,在项目开发中,运用到了单例模式。因为其表达方式与Java是不同的。所以对不同单例模式的实现进行了分别探讨。主要单例模式实现如下:

  • 饿汉式
  • 懒汉式
  • 线程安全的懒汉式
  • 双重校验锁式
  • 静态内部类式

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对应字节码

我们进入我们的Android Studio(我的Android Studio 3.0,如果你的编译器版本过低,请自动升级) 选择Tools工具栏,选择"Kotlin",选择“Show Kotlin Bytecode"

选择过后就会进入到下方界面:

点击"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(instance==null){
   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 <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =
  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<out T> {
  //当前实例化对象,一旦实例化后,该对象不会再改变
 public val value: T
 //返回true表示,已经延迟实例化过了,false 表示,没有被实例化,
 //一旦方法返回true,该方法会一直返回true,且不会再继续实例化
 public fun isInitialized(): Boolean
}

继续查看SynchronizedLazyImpl,具体实现如下:

SynchronizedLazyImpl内部实现

private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, 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 <T> Lazy<T>.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 仿开眼的项目SimpleEyes(ps: 其实在我之前,已经有很多小朋友开始仿这款应用了,但是我觉得要做就做好。所以我的项目和其他的人应该不同,不仅仅是简单的一个应用。但是,但是。但是。重要的话说三遍。还在开发阶段,不要打我),欢迎大家follow和start

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对脚本之家的支持。

更多精彩内容其他人还在看

使用ViewPager实现android软件使用向导功能实现步骤

现在的大部分android软件,都是使用说明,就是第一次使用该软件时,会出现向导,可以左右滑动,然后就进入应用的主界面了,下面我们就实现这个功能
收藏 0 赞 0 分享

android在异步任务中关闭Cursor的代码方法

android在异步任务中如何关闭Cursor?在我们开发应用的时候,很多时候会遇到这种问题,下面我们就看看代码如何实现
收藏 0 赞 0 分享

Android自定义桌面功能代码实现

android自定义桌面其实很简单,看一个例子就明白了
收藏 0 赞 0 分享

android将图片转换存到数据库再从数据库读取转换成图片实现代码

有时候我们想把图片存入到数据库中,尽管这不是一种明智的选择,但有时候还是不得以会用到,下面说说将图片转换成byte[]数组存入到数据库中去,并从数据库中取出来转换成图像显示出来
收藏 0 赞 0 分享

TextView显示系统时间(时钟功能带秒针变化

用System.currentTimeMillis()可以获取系统当前的时间,我们可以开启一个线程,然后通过handler发消息,来实时的更新TextView上显示的系统时间,可以做一个时钟的功能
收藏 0 赞 0 分享

Android用ListView显示SDCard文件列表的小例子

本文简单实现了用ListView显示SDCard文件列表,目录的回退等功能暂不讨论,获取文件列表,files即为所选择目录下的所有文件列表
收藏 0 赞 0 分享

Android拦截外拨电话程序示例

这篇文章主要介绍了Android拦截外拨电话的示例,大家参考使用吧
收藏 0 赞 0 分享

通过Html网页调用本地安卓(android)app程序代码

如何使用html网页和本地app进行传递数据呢?经过研究,发现还是有方法的,总结了一下,大致有一下几种方式
收藏 0 赞 0 分享

android Textview文字监控(Textview使用方法)

以手机号充值为例,当用户输入最后一位数时候,进行汇率的变换,本文就实现类似这样的功能
收藏 0 赞 0 分享

Android ListView长按弹出菜单二种实现方式示例

这篇文章主要介绍了Android ListView长按弹出菜单的方法,大家参考实现
收藏 0 赞 0 分享
查看更多