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翻译过去的,流程入下图。
首先我们注意Kotlin其中几个有意思的特性。
- 变量会自动创建并隐藏get/set方法,这里巧妙运用了这点,在get方法中判断对象是否存在并获取。
- Kt中,object 关键字声明,其内部不允许声明构造方法,使用有点类似匿名内部类的使用,所以大家一般也是用object来声明单例。
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
得到字节码如下:
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()
方法,虽然无伤大雅,但是凭着精益求精的原则,我们试着干掉他。理下思路:
- 既然我们的
get()
是多余的,我们就干掉他。 - 字节码中
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,也叫双检锁/双重校验锁俩个关键点:
volatile
关键字。简单说某个线程改变了其所修饰的变量,立马在内存中刷新,其他线程同步可知。- 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 又到了这个点,明天就是 六一儿童节,过节过节~~~