Kotlin学习笔记(一):深入理解单例类的实现

239 阅读3分钟

1. 什么是单例模式

单例模式(Singleton Pattern)是软件工程中常用的设计模式,当我们在一个工程确保一个类只想有一个实例的情况下,需要实现全局只有一个访问点。在不同语言中的实现都大同小异。单例类需要注意以下几点:

  1. 单例类(Singleton Class)只能拥有一个实例(instance)。
  2. 单例类必须自己创建唯一实例。
  3. 单例类必须给其他对象提供实例且唯一访问入口。

单例模式的核心其实便是限制类的实例化,常用于服务,数据库等全局共享的情况。

2.Kotlin单例模式的实现

2.1 对象表达式

Kotlin提供了关键字object帮助开发者以一种简洁的方式来创建单例。

val singleton = object{
    val testStr = "This is a Singleton Class"
    fun printFun(){
        println(testStr)
    }
}
fun main(){
    singleton.printFun()
}

在JVM上等效于以下Java代码:

public final class Singleton {
    private final String testStr = "This is a Singleton Class";

    private Singleton() {
    }

    public final void printFun() {
        System.out.println(testStr);
    }

    public static final Singleton getInstance() {
        return Holder.INSTANCE;
    }

    private static final class Holder {
        private static final Singleton INSTANCE = new Singleton();
    }
}

Singleton拥有私有化的构造函数防止外部进行实例化,并在内部定义了静态内部类Holder,最后提供了一个get访问接口供其他对象使用。

2.2 伴生对象

伴生对象(Companion Object)写在类内部的与类紧密相关的静态成员,这些成员在类加载时就被初始化并且是单例的。对比于Java的静态内部类,主要区别于访问方式的不同:

  • 伴生对象 的成员可以通过外部类的名称直接访问,如 MyClass.companionMethod()
  • 静态内部类 的静态成员需要通过静态内部类的实例来访问,如 MyClass.MyStaticClass.staticMethod()
class MyClass {
    companion object {
        fun staticMethod() { }
        val staticProperty: String = "Hello"
    }
}

// 在 JVM 字节码中,上面的代码大致会被编译成如下形式:
public final class MyClass {
    public static final MyClass.Companion Companion = new MyClass.Companion((DefaultConstructorMarker)null);

    public static final class Companion {
        public final void staticMethod() {
            // ...
        }
        @NotNull
        public final String getStaticProperty() {
            return "Hello";
        }
        private Companion() {
            // ...
        }
        public Companion(DefaultConstructorMarker $constructor_marker) {
            this();
        }
    }
}

2.3 枚举创建

在 Kotlin 中,枚举类型默认是单例的。每个枚举常量都是唯一的,并且枚举类会自动实现 Singleton

enum class SingletonEnum {
    INSTANCE;
    var someProperty: String = "Some Value"
    fun someFunction() {
        println("Singleton function")
    }
}

fun main() {
    SingletonEnum.INSTANCE.someFunction()
}

2.4 懒汉式加载

有时候我们不需要单例立马创建,防止过早的占用资源或者构造参数的未初始化,只希望在第一次使用的时候创建实例。那么前几个方式都可以叫做饿汉式加载,在类加载时便创建实例。

Kotlin提供了lazy()函数以及by关键字来实现。

lazy()函数中可以在多线程环境下接受LazyThreadSafetyMode.SYNCHRONIZED参数指定只有一个线程可以初始化,保证线程安全。

class Singleton private constructor() {
    companion object {
        val instance: Singleton by lazy {
            Singleton()
        }
    }

    fun someFunction() {
        println("Singleton function is called")
    }
}

fun main() {
    // 在第一次访问时,Singleton 的实例将被创建
    val singleton = Singleton.instance
    singleton.someFunction()
}

2.5 双重检查锁

  • 使用@Volatile关键字确保instance字段的可见性,防止多线程环境下出现指令重排序导致的问题。
  • 通过双重检查(Double-Checked)机制,在第一次调用getInstance()时进行加锁,确保只有一个线程能够创建实例。
class Singleton private constructor() { 
    companion object { 
        @Volatile private var instance: Singleton? = null 
           fun getInstance(): Singleton { 
           return instance ?: synchronized(this) { 
           instance ?: Singleton().also { instance = it } 
           } 
       } 
   } 
}