Kotlin中的单例类

97

我想知道如何在 Kotlin 中创建单例类,以便我的 Util 类每次应用程序执行时只实例化一次。然而,当我将我的 Java 类转换为 Kotlin 时,下面的代码被生成。

这个代码是正确的吗?

companion object {
    private var utilProject: UtilProject? = null

    val instance: UtilProject
        get() {
            if (utilProject == null) utilProject = UtilProject()
            return utilProject!!
        }
} 

我找到了一个相关的问题,但它是带参数的,而且我没有弄清楚如何在没有参数的情况下进行转换。

10个回答

109

Kotlin中有一个专门用于单例的关键字object。您只需输入以下简单内容即可获得工作单例类:

Kotlin中有一种特殊的关键字object可以用于创建单例。只需编写以下简单代码即可创建一个可用的单例类:

object MySingleton

或者当你需要一些成员函数时:

object MySingleton {
    fun someFunction(...) {...}
}

然后使用它:

MySingleton.someFunction(...)

这里有一个参考:https://kotlinlang.org/docs/reference/object-declarations.html#object-declarations

编辑:

在你的情况下,你只需要将class UtilProject的定义替换为以下内容:

object UtilProject {

    // here you put all member functions, values and variables
    // that you need in your singleton Util class, for example:

    val maxValue: Int = 100

    fun compareInts(a: Int, b: Int): Int {...}
}

然后您可以在其他地方简单地使用您的单例:

UtilProject.compareInts(1, 2)
//or
var value = UtilProject.maxValue

我不明白这个。这对我的Util类有什么用处? - Khemraj Sharma
不要创建 companion object,只需将 class UtilProject 的声明更改为 object UtilProject - dey
喜欢@Naetmul的回答吗? - Khemraj Sharma
4
实际上,使用object代替class就是单例模式。上面提到的参考链接值得一读 ;-) - Roland
2
使用object而不是class是最优化的解决方案。如果您想从JAVA类中调用对象内的方法,可以使用Singleton.INSTANCE.methodName(). - Ahmed Nezhi
显示剩余2条评论

88

只是

companion object {
    val instance = UtilProject()
} 

由于伴生对象本身是一种语言级别的单例,所以将起作用。
(当首次调用伴生对象时将创建instance。)

-- 更新 --

如果您需要控制单例对象何时初始化,则可以为每个类创建一个对象。

class UtilProject {
    ....
    companion object {
        val instance = UtilProject()
    }
}

class AnotherClass {
    ...
    companion object {
        val instance = AnotherClass()
        const val abc = "ABC"
    }
}

fun main(args: Array<String>) {
    val a = UtilProject.instance // UtilProject.instance will be initialized here.
    val b = AnotherClass.abc // AnotherClass.instance will be initialized here because AnotherClass's companion object is instantiated.
    val c = AnotherClass.instance
}

这里,AnotherClass.instance 在实际调用之前被初始化。当调用 AnotherClass 的伴生对象时,它被初始化。 为了防止在需要时之前进行初始化,可以像这样使用:

class UtilProject {
    ....
    companion object {
        fun f() = ...
    }
}

class AnotherClass {
    ...
    companion object {
        const val abc = "ABC"
    }
}

object UtilProjectSingleton {
    val instance = UtilProject()
}

object AnotherClassSingleton {
    val instance = AnotherClass()
}

fun main(args: Array<String>) {
    UtilProject.f()
    println(AnotherClass.abc)

    val a = UtilProjectSingleton.instance // UtilProjectSingleton.instance will be initialized here.
    val b = AnotherClassSingleton.instance // AnotherClassSingleton.instance will be initialized here.

    val c = UtilProjectSingleton.instance // c is a.
}
如果您不关心每个单例何时初始化,也可以像这样使用它:
class UtilProject {
    ....
    companion object {
        fun f() = ...
    }
}

class AnotherClass {
    ...
    companion object {
        const val abc = "ABC"
    }
}

object Singletons {
    val utilProject = UtilProject()
    val anotherClass = AnotherClass()
}

fun main(args: Array<String>) {
    val a = Singletons.utilProject
    val b = Singletons.anotherClass 
}

简而言之,
在 Kotlin 中,objectcompanion object 是一个单例对象。
您可以在 objectobjects 中分配变量,然后像单例一样使用这些变量。

objectcompanion object 在第一次使用时实例化。 object 中的 valvar 在第一次实例化该 object 时进行初始化(即第一次使用该 object 时)。

编辑: William Hu 在评论中说,“companion object 是在加载类时发生的。”


所有对象都在companion单例中吗? - Khemraj Sharma
@Khemraj 不是的。companion object 是一个单例对象。如果你需要许多不同的单例,你可以为每个单例创建许多对象。我会更新答案。 - Naetmul
伴生对象是一个单例对象。如果您需要许多不同的单例对象,则可以为每个单例对象创建许多对象。 - 嘿嘿,好笑啊兄弟。 - i30mb1
1
单例模式的初始化怎么办?如果 object 不能有构造函数,我们应该使用初始化块 (init {...}) 吗? - Paulo Merson
1
当第一次使用对象或伴生对象时,对象会被实例化。这并不正确,object是这样的,但是companion object是在类加载时实例化的。 - William Hu
显示剩余2条评论

14

超级简单的懒惰示例:

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

5
抱歉,我不明白您需要翻译的内容。请提供更多的信息或指示。 - roghayeh hosseini
真的。实际上,object关键字也是一样的。 - Michał Powłoka

8

只需要使用对象这个词。

object UtilProject {
    var bar: Int = 0
    fun foo() {        
    }
}

您可以直接访问仅拥有一个实例的对象。

fun main(args: Array<String>) {
    UtilProject.bar = 1
    println(UtilProject.bar)    
}

3

这将会有所帮助。我正在使用Dialog类,但是您可以使用此示例来了解如何实现。

class MyClass(context: Context) : Dialog(context) {
    companion object {
    lateinit var INSTANCE: MyClass

    @JvmStatic
    fun getInstance(context: Context): MyClass{
        if (!::INSTANCE.isInitialized) {
            INSTANCE = MyClass(context)
        }

        return INSTANCE
    }
}}

3
 class TestMySingleton private constructor() {
​
   companion object {
        var single = TestMySingleton()

        fun getInstance(): TestMySingleton {
            if (single == null)
                single = TestMySingleton()
            return single
        }
    }

}

2

带有参数的变量

open class SingletonHolder<out T: Any, in A>(creator: (A) -> T) {
    private var creator: ((A) -> T)? = creator
    @Volatile private var instance: T? = null

    fun getInstance(arg: A): T {
        val checkInstance = instance
        if (checkInstance != null) {
            return checkInstance
        }

        return synchronized(this) {
            val checkInstanceAgain = instance
            if (checkInstanceAgain != null) {
                checkInstanceAgain
            } else {
                val created = creator!!(arg)
                instance = created
                creator = null
                created
            }
        }
    }
}



2
这个回答很好,但它只是整个事情的一部分。这段代码是从这里复制的: https://blog.mindorks.com/how-to-create-a-singleton-class-in-kotlin 仔细看看以获取完整的图像,并了解为什么这是处理它的方法。 - C.Schone

2
一个单例模式的示例,用于支持api调用的retrofit。最初的回答。
object RetrofitClient {

    private var instance: Api? = null
    private val BASE_URL = "https://jsonplaceholder.typicode.com/"

    fun getInstance(): Api? {
        if (instance == null) {
            val retrofit = Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build()
            instance = retrofit.create(Api::class.java)
        }
        return instance
    }
}

1
你实现 getInstance() 方法的方式不正确。getInstance() 内部的逻辑表明它将始终返回 non-null 值,但是你的返回类型 (Api?) 表示它可以返回 null 值。如果你尝试重构返回类型为 Api,你会收到 无法对可变属性进行智能转换 的警告。 - Farid
这是一个有缺陷的实现。它缺少同步。而且没有理由需要返回可空值。 - Tenfour04

1
class MyClass {


    init {
        println("init is called")
    }

    companion object {

        private var obj: MyClass? = null
        fun getInstance(): MyClass {
            if (obj == null) {
                obj = MyClass()
            }
            return obj as MyClass 
        }

    }

    fun printHello() {
        println("Hello World")
    }

你可以通过 MyClass.getInstance() 这样的方式创建它的实例,类似于Java。

0

除了线程处理的情况,这里的所有答案大多数都是正确的。我的用例是这样的:

使用不同的线程同时调用这两种方法:

private fun getProductListSync() {
    launch(Dispatchers.Main) {
        products = withContext(Dispatchers.IO) { getProducts() }
    }
}

private suspend fun getProducts(): List<Product>? {
    val client = APIUtils.getClient() // this method is used for getting Retrofit Client
    val productListCall = client.create(APIBuilder::class.java).getProductList()
    return if (productListCall.isSuccessful) {
        ...
    } else {
        ...
    }
}


private fun getRestaurantDetailsSync() {
    launch(Dispatchers.Main) {
        storeInfo = withContext(Dispatchers.IO) { getStoreInfo() }
    }
}

private suspend fun getStoreInfo(): StoreInfo? {
    val client = APIUtils.getClient()
    val storeInfoCall = client.create(APIBuilder::class.java).getStoreInfo()
    return if (storeInfoCall.isSuccessful) {
        ...
    } else {
        ...
    }
}

调用代码:

getRestaurantDetailsSync()
getProductListSync()

APIUtils的正确代码,用于单例模式和多线程处理

APIUtils.kt

object APIUtils {

    @Volatile
    private var retrofit: Retrofit? = null

    /**
     * You can create multiple methods for different BaseURL
     *
     * @return [Retrofit] object
     */
    @Synchronized
    fun getClient(): Retrofit {
        if (retrofit == null) {
            retrofit = Builder()
                .baseUrl(Constants.API.BASE_URL)
                .build()
        }
        return retrofit!!
    }

    fun destroy() {
        retrofit = null
    }
}

注意:如果我们在字段上不使用@Volatile,以及在函数上不使用@Synchronized,那么当从不同的线程调用时,它将创建多个retrofit字段的副本。
您还可以重新分配retrofit客户端以应用其他静态标头,因为我们使用了“var”关键字而不是“val”或“lateinit var”。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接