Kotlin中的单例类

289 阅读4分钟

在Kotlin中,定义一个单例类非常简单且直观,得益于语言内置的object关键字。这个关键字不仅简化了单例的实现,还避免了传统Java单例模式中的样板代码。
1,在kotlin中通过object定义一个单例类。
在一个HelloSingleton.kt文件中,定义两个单例类:

object HelloSingleton {
    private var sProp:String = "hello singleton"
    fun sayHello() {
        println(sProp)
    }
}

object MySingleton {
    // 类的成员变量
    var someProperty: String = "Hello, World!"

    // 类的成员函数
    fun doSomething() {
        println("Doing something in MySingleton!")
    }
}

fun main() {
    println("=======================")
    HelloSingleton.sayHello()
    MySingleton.doSomething()
    MySingleton.someProperty = "new Value"
    println(MySingleton.someProperty)
}

HelloSingleton和MySingleton都是通过object关键字定义的,它自动成为了一个单例,这意味着在Kotlin程序的整个生命周期中,HelloSingleton和MySingleton分别只会有一个实例。
在main函数中,我们直接通过MySingleton的类名来访问其属性和方法,无需进行实例化。这体现了Kotlin单例实现的简洁性。
上面定义的单例类是饿汉式单例。

2,结合类的私有构造函数和伴生对象来实现单例模式。
不带参数的方式

class Singleton private constructor() {
    // 类的成员
    fun doSomething() {
        println("Doing something in Singleton!")
    }

    // 伴生对象,提供对单例实例的访问
    companion object {
        // 使用by lazy实现延迟初始化,确保线程安全
        val instance: Singleton by lazy { Singleton() }
    }
}

在main()函数中,

Singleton.instance.doSomething()

下面看下带参数的方式:

class SingletonA private constructor(id: String) {
	val instanceId: String = id
    companion object {
        private var mInstance: SingletonA? = null

        @Synchronized
        fun getInstance(id: String): SingletonA {
            return mInstance ?: SingletonA(id).also { mInstance = it }
        }
    }
}

在main()函数中,

println(SingletonA.getInstance("hello").instanceId)

这样的方式可以实现懒汉单例模式,但推荐采用第一种object的方式定义单例。

3,对于object,除了定义单例类对象外,还可以用于修饰对象表达式。

open class SingleTest {
    open fun play() = "loading..."
}

之后在main函数中

fun main() {
	//使用对象表达式创建了一个SingleTest的匿名子类实例,并重写了play()方法
    val p = object : SingleTest(){
        override fun play() = "other loading..."
    }
    println(p.play()) //输出other loading...
}

在上面的代码中,定义了一个open类型的类。
在Kotlin中,定义一个类时,你可以通过指定关键字来控制这个类的可继承性。主要有两种类型的类:final(默认,不可被继承)和open(可被继承)。
Kotlin中的抽象类(使用abstract关键字)默认也是开放的,因为它们的主要目的之一就是作为其他类的基类。

abstract class MyAbstractClass {
    // 抽象成员
    abstract fun myAbstractFunction(): String

    // 非抽象成员(可以有默认实现)
    fun myNonAbstractFunction() {
        // 函数实现
    }
}

即使在开放类中,也可以通过省略open关键字来定义封闭的函数和属性,这意味着它们不能在子类中被重写或修改。

open class MyClassWithFinalMember {
    final val myFinalProperty = "I cannot be overridden"

    fun myFinalFunction() {
        // 这个函数不能被重写
    }
}

上面的代码中,属性和方法定义省略了open,那么myFinalProperty是一个封闭的属性,而myFinalFunction是一个封闭的函数。尝试在子类中重写myFinalFunction或修改myFinalProperty的值将导致编译错误。
在Kotlin中,你可以通过open关键字来定义一个可以被继承的类,而省略该关键字则会使类默认不可被继承。对于函数和属性,你也可以通过省略open关键字来使它们封闭(不可被重写或修改)。

在 Kotlin 中,使用 object 关键字可以方便地创建一个匿名内部类的实例。

private val pageChangeListener = object : ViewPager.OnPageChangeListener {
    override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
        Log.v(TAG, "pageChangeListener,onPageScrolled...position:$position")
    }

    override fun onPageSelected(position: Int) {
        Log.v(TAG, "pageChangeListener,onPageSelected...position:$position")
    }

    override fun onPageScrollStateChanged(state: Int) {
        Log.v(TAG, "pageChangeListener,onPageScrollStateChanged...state:$state")
    }
}

上面的代码将 ViewPager.OnPageChangeListener 的匿名内部类转换为 Kotlin 的对象表达式。

4,访问单例类的函数
从上面已经知道在 Kotlin 里,使用 object 关键字来定义单例类。那怎么访问单例类中的函数呢?
在kotlin类文件中,要访问单例类的函数,无需创建该类的实例,直接通过单例类的名称来调用函数即可。即 ”单例类类名“."函数"。
在java类文件中,要访问单例类的函数,需要使用 INSTANCE 字段。即”单例类类名“.INSTANCE."函数"。
在 Java 里,单例模式通常需要手动编写代码来保证类只有一个实例,并且会提供一个静态的 INSTANCE 字段来获取该实例。
在 Kotlin 中,使用 object 关键字定义单例类时,Kotlin 编译器会自动处理单例的创建和访问,不需要显式地使用 INSTANCE 字段。
kotlin单例类与 Java 代码交互:
如果项目是 Kotlin 和 Java 混合编程,在 Java 代码中访问 Kotlin 的单例类时,就需要使用 INSTANCE 字段。 因为 Kotlin 的 object 单例类在编译成 Java 字节码后,会生成一个静态的 INSTANCE 字段来表示单例实例。