1. 什么是单例类?

单例类是一种设计模式,用于限制类只能被实例化一次。换句话说,单例类在整个应用生命周期中只能存在一个实例。任何后续尝试创建实例的操作都会返回最初创建的那个实例。

使用单例的场景通常包括数据库连接、配置管理器、日志服务等。通过限制类只能被实例化一次,可以确保应用状态的一致性,避免多个实例之间可能产生的冲突。

2. Kotlin 中如何实现单例类?

Kotlin 提供了多种方式来实现单例类,下面我们将逐一介绍这些实现方式,并给出对应的代码示例和说明。

2.1. 使用 Companion Object

在 Kotlin 中,可以通过 companion object 来实现单例。companion object 是类的伴生对象,常用于存放静态成员和方法。

以下是一个线程安全的单例实现:

class Singleton private constructor() {

    companion object {

        @Volatile
        private var instance: Singleton? = null

        fun getInstance() =
            instance ?: synchronized(this) {
                instance ?: Singleton().also { instance = it }
            }
    }

    fun doSomething() = "Doing something"
}

获取实例的方式如下:

val instance = Singleton.getInstance()

✅ 优点:线程安全
❌ 缺点:实现略复杂

2.2. 饿汉式(Eager Initialization)

使用 object 关键字可以快速声明一个单例类,这是 Kotlin 特有的方式,属于饿汉式实现,即在类加载时就完成初始化。

object Singleton {
    fun doSomething() = "Doing something"
}

调用方式非常简洁:

val instance = Singleton

✅ 优点:写法简单,线程安全
❌ 缺点:无法延迟加载

2.3. 懒加载(Lazy Initialization)

使用 lazy 委托可以实现懒加载的单例,即在第一次访问时才初始化实例。

class Singleton private constructor() {

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

    fun doSomething() = "Doing something"
}

调用方式如下:

val instance = Singleton.instance

⚠️ 注意:这种方式默认不是线程安全的,如需线程安全,需配合锁机制。

2.4. 双重检查锁定(Double-Checked Locking)

双重检查锁定是一种优化的线程安全实现方式,避免每次调用都加锁,只在必要时才同步。

class Singleton private constructor() {
    companion object {
        @Volatile
        private var instance: Singleton? = null

        fun getInstance(): Singleton {
            if (instance == null) {
                synchronized(this) {
                    if (instance == null) {
                        instance = Singleton()
                    }
                }
            }
            return instance!!
        }
    }

    fun doSomething() = "Doing something"
}

调用方式如下:

val instance = Singleton.getInstance()

✅ 优点:线程安全,性能较好
❌ 缺点:实现稍复杂

2.5. 使用枚举(Enum)

使用枚举也可以实现单例,并且默认就是线程安全的,实现也非常简单。

enum class Singleton {
    INSTANCE;

    fun doSomething() = "Doing something"
}

调用方式如下:

val instance = Singleton.INSTANCE

✅ 优点:线程安全,防止反射攻击
❌ 缺点:可扩展性较差

3. 单例类的优缺点

3.1. 优点

  • 唯一实例:确保类在整个应用中只有一个实例,便于状态管理。
  • 易于访问:可以通过静态方法快速获取实例,方便调用。
  • 提升性能:避免重复创建对象,节省内存和初始化时间。
  • 状态一致性:全局唯一实例有助于保持状态一致,减少错误。

3.2. 缺点

  • 难以测试:全局状态容易造成测试用例之间相互干扰。
  • 隐藏依赖:单例的使用可能掩盖类之间的依赖关系。
  • 耦合度高:过度使用会导致模块之间耦合度高,影响可维护性。

4. 总结

在 Kotlin 中,实现单例类的方式有多种,各有优劣。以下是一些常见方式的对比:

实现方式 是否线程安全 是否延迟加载 是否推荐使用
companion object + synchronized
object 关键字
lazy 委托 ❌(需手动处理)
双重检查锁定
枚举

选择哪种方式取决于具体场景和需求。如果是简单场景,推荐使用 objectenum;如果需要延迟加载或更复杂的控制,可以选择 lazy 或双重检查锁定。

推荐实践

  • 对于大多数业务场景,优先使用 object,简洁且线程安全。
  • 需要延迟加载时,使用 lazy 或双重检查锁定。
  • 需要防范反射攻击时,考虑使用枚举。

完整示例代码可在 GitHub 上找到。


原始标题:Singleton Classes in Kotlin