Android Kotlin学习笔记(Java与Kotlin的单例模式比较)

概念引入

Java中最简单的设计模式之一,这种模式保证创建自身类的对象只有一个,可以直接访问其中方法自动创建并获得自身对象,不需要直接实例化。因此,单例模式也是创建者模式的一种。
我感觉我的描述不够准确,但是这篇文字主要是比较Kotlin与Java单例模式的代码比较。详细概念请参考:菜鸟教程
言归正传,我们主要从单例模式的类型开始一一比较

1.懒汉式

懒汉式其实分俩种,线程安全的和线程不安全的,区别就是synchronized是否存在。为什么叫懒汉式,因为首次创建时候才去创建对象,所以他是懒方法,懒加载概念就出来了,lazy loading~

1.1 线程不安全的懒汉式

作为最基本的实现方式之一,因为synchronized的锁关键字,所以不能在多线程下工作,线程不安全

Java下的实现

没必要多说了,直接看代码实现和注释。

public class SingletonLazy1 {
    private static SingletonLazy1 instance; //单例静态变量
    private SingletonLazy1 (){} //构造私有化

    public static SingletonLazy1 getInstance() {
        if (instance == null) {//不存在就创建
            instance = new SingletonLazy1();
        }
        return instance;
    }
}

Kotlin下的实现

原谅我是直接代码在android studio翻译过去的,流程入下图。
Java转kotlin
首先我们注意Kotlin其中几个有意思的特性。

  1. 变量会自动创建并隐藏get/set方法,这里巧妙运用了这点,在get方法中判断对象是否存在并获取。
  2. Kt中,object 关键字声明,其内部不允许声明构造方法,使用有点类似匿名内部类的使用,所以大家一般也是用object来声明单例。
  3. Object声明的类,无法创建空的构造函数,所以我们也无法private修饰
object SingletonLazy1 {

    var instance //单例静态变量
            : SingletonLazy1? = null
        get() {
            if (field == null) { //不存在就创建
                field = SingletonLazy1
            }
            return field
        }
        private set
}

暂且看一看具体到class里面的实现是什么样子的。通过编译器直接查看class字节码,Tools→Kotlin→Show Kotlin ByteCodes→新窗口中的Decompile
1
2
得到字节码如下:

public final class SingletonLazy1 {
   @Nullable
   private static SingletonLazy1 instance;
   public static final SingletonLazy1 INSTANCE;

   @Nullable
   public final SingletonLazy1 getInstance() {
      if (instance == null) {
         instance = INSTANCE;
      }

      return instance;
   }

   private SingletonLazy1() {
   }

   static {
      SingletonLazy1 var0 = new SingletonLazy1();
      INSTANCE = var0;
   }
}

我们会发现会有俩个单例变量,为什么呢?这就是由于object关键字声明类似匿名内部类,本身可以作为单例模式(饿汉模式)。如果仅仅只是实现非线程安全的单例模式,我们仅仅需要Object关键字创建类即可。
既然说到懒汉式,我们就暂时抛开object,修改下代码,改成非线程安全的懒汉模式。代码如下,注意get()方法命名为getInstance()时候报错,我猜是隐藏了同名函数。

class SingletonLazy1 private constructor(){
    companion object {
        private var instance: SingletonLazy1? = null
            get() {
                if (field == null) {
                    field = SingletonLazy1()
                }
                return field
            }

        fun get(): SingletonLazy1 {//起名为getInstance报错,估计内部隐藏了同名函数
            return instance!!
        }
    }
}

比葫芦画瓢,看下字节码。删掉@Metadata等印象阅读的部分。

public final class SingletonLazy1 {
   private static SingletonLazy1 instance;
   public static final SingletonLazy1.Companion Companion = new SingletonLazy1.Companion((DefaultConstructorMarker)null);

   private SingletonLazy1() {
   }

   // $FF: synthetic method
   public SingletonLazy1(DefaultConstructorMarker $constructor_marker) {
      this();
   }

   public static final class Companion {
      private final SingletonLazy1 getInstance() {
         if (SingletonLazy1.instance == null) {
            SingletonLazy1.instance = new SingletonLazy1((DefaultConstructorMarker)null);
         }
         return SingletonLazy1.instance;
      }

      private final void setInstance(SingletonLazy1 var1) {
         SingletonLazy1.instance = var1;
      }
      
      @NotNull
      public final SingletonLazy1 get() {
         SingletonLazy1 var10000 = ((SingletonLazy1.Companion)this).getInstance();
         if (var10000 == null) {
            Intrinsics.throwNpe();
         }
         return var10000;
      }

      private Companion() {
      }
      // $FF: synthetic method
      public Companion(DefaultConstructorMarker $constructor_marker) {
         this();
      }
   }
}

看到后发现果真有getInstance()的方法。这时候我们可以注意下Kotlin的companion关键字,他所修饰的object为伴生对象,伴生对象在类中只能存在一个,我们可以暂时理解为companion类似于static修饰。既然我们多写了等于getInstance()get()方法,虽然无伤大雅,但是凭着精益求精的原则,我们试着干掉他。理下思路:

  1. 既然我们的get()是多余的,我们就干掉他。
  2. 字节码中getInstance()方法的修饰为private,外界肯定调用不到,修改为public
class SingletonLazy1 private constructor(){
    companion object {
        var instance: SingletonLazy1? = null	//Kotlin中public可以隐藏
            get() {
                if (field == null) {
                    field = SingletonLazy1()
                }
                return field
            }
    }
}

如上代码所示,就是一个完整简洁的Kotlin实现。大家也可以再去查看下字节码比对一下。由于companion关键字静态修饰的绝妙,调用也很简单:SingletonLazy1.Companion.getInstance();

1.1 线程安全的懒汉式

对比线程不安全,仅仅就是在获取单例的public方法加了synchronized关键字锁住。

Java下的实现

public class Single {
    private static Single instance;////单例静态变量

    private Single() { }

    public static synchronized Single getInstance() {//synchronized锁
        if (instance == null) {//不存在创建
            instance = new Single();
        }
        return instance;
    }
}

kotlin下的实现

同样的转换为Kotlin仍然会转换为object,算了还是自己手写吧。其实很简单,Kotlin中有@Synchronized等同于synchronized关键字,可以用来修饰方法。也可以指定@get:Synchronized,
俩种作用一样的,任选其一。

class SingletonLazy1 private constructor(){

    companion object {
        @get:Synchronized
        var instance: SingletonLazy1? = null
            //@Synchronized
            get() {
                if (field == null) {
                    field = SingletonLazy1()
                }
                return field
            }
    }
}

查看下字节码比对一下,准确无误。抬走,下一个。

2.饿汉式

缺点:类加载时就初始化,浪费内存。

它通过类加载时就初始化,机制避免了多线程的同步问题(无锁,执行效率高),同样的instance 在类装载时就实例化,没有达到 lazy loading 的效果。同时,无论你用或者不用,他就是已经完成初始化了,浪费资源
我们在1.1 线程不安全的懒汉式中已经提到object关键字和懒汉式了,这里就不在文字叙述了,注意object关键字,官方文档看下资料就可以了,直接上代码。

Java下的实现

public class Single2 {
    private static Single2 instance = new Single2(); 	//变量私有
    private Single2 (){} 	//构造方法私有
    public static Single2 getInstance() {
        return instance;
    }
}

Kotlin下的实现

object Single2{
    fun test(): Unit {}
}

调用也非常简单Single2.test()

3. DCL

即 double-checked locking,也叫双检锁/双重校验锁俩个关键点:

  1. volatile关键字。简单说某个线程改变了其所修饰的变量,立马在内存中刷新,其他线程同步可知。
  2. synchronized 锁在其中的机制,锁的是这个类的操作。

Java下的实现

public class SingleDouble {
    private volatile static SingleDouble singleton;	//	volatile修饰 线程同步

    private SingleDouble() {}//	私有不可访问

    public static SingleDouble getSingleton() {
        if (singleton == null) {
            synchronized (SingleDouble.class) {// synchronized
                if (singleton == null) {
                    singleton = new SingleDouble();
                }
            }
        }
        return singleton;
    }
}

Kotlin下的实现

直接转换为Kotlin,修改object为class加上私有构造器,代码如下:

class SingleDouble private constructor(){
    // synchronized
    @Volatile
    var singleton //	volatile修饰 线程同步
            : SingleDouble? = null
        get() {
            if (field == null) {
                synchronized(SingleDouble::class.java) {
                    if (field == null) {
                        field = SingleDouble()
                    }
                }
            }
            return field
        }
        private set

}

乍眼一看,没啥问题,其实也没啥问题,但是如果这样写,就真的太low了,我们引入Kotlin中独特的懒加载,Lazy关键字。他属于代理的一种模式,用于生命周期类中延迟初始化一些对象。比如我们现在需要第一次调用时候才实例,本身就属于一个懒加载。

class SingleDouble private constructor(){
    companion object {
        val instance: SingleDouble by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
            SingleDouble() }
    }
}

这种方式是网上公推的,其实我也看得迷迷糊糊的,点到LazyThreadSafetyMode.SYNCHRONIZED里面,其实说的很直白:锁用于确保只有一个线程可以初始化[Lazy]实例。这句话基本上就概括了DCL中我们所想要的,有锁,确保单个线程对实例的操作,延迟加载。

4. 静态内部类

主要用它跟饿汉式对比,饿汉式只要类被加载,instance就会被实例化,而这种方式加载的时候instance 不一定被初始化。只有调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。

Java下实现

public class Single3 {
    private static class SingletonHolder {
        private static final Single3 INSTANCE = new Single3();
    }

    private Single3() {}

    public static final Single3 getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

Kotlin下实现

这个差异性就不大了,大家看代码基本就能看个七七八八了。

class Single3 private constructor(){

    companion object {
        val instance = SingletonHolder.holder
    }

    private object SingletonHolder {
        val holder= Single3()
    }

}

5. 枚举

我懒得写,懂我的人一定知道我为什么懒得写,因为个人喜好,我特别讨厌枚举,除了看着爽一点(我看着也不爽),其实对性能没有提升(甚至有一定的降低),当然这些都无关痛痒,主要原因就是我不喜欢。

2020年5月31日23:58:25 又到了这个点,明天就是 六一儿童节,过节过节~~~

猜你喜欢

转载自blog.csdn.net/ma598214297/article/details/106460934