Kotlin 中的单例类

Posted

技术标签:

【中文标题】Kotlin 中的单例类【英文标题】:Singleton class in Kotlin 【发布时间】:2019-01-20 22:21:27 【问题描述】:

我想知道如何在 Kotlin 中创建一个单例类,以便我的 Util 类在每次应用执行时只实例化一次。但是,当我将我的 Java 类转换为 kotlin 时,生成了以下代码。

这是正确的吗?

companion object 
    private var utilProject: UtilProject? = null

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

我可以找到一个相关的 question,但它是带参数的,如果没有参数,我无法将其转换。

【问题讨论】:

【参考方案1】:

在 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 类有何用处? 反对创建companion object,只需将class UtilProject的声明更改为object UtilProject 喜欢@Naetmul 的回答? 实际上,使用object 而不是class 是你的单身。 mentioned reference above 值得一读;-) 使用object 代替class 是最优化的解决方案。如果您想从 JAVA 类调用对象内部的方法,可以使用 Singleton.INSTANCE.methodeName()..【参考方案2】:

只是

companion object 
    val instance = UtilProject()
 

将完成这项工作,因为 伴随对象 本身是语言级别的单例。 (instance 将在 first 调用伴生对象时创建。)

-- 更新了--

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

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.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 

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

objectcompanion object 在第一次使用时被实例化。 object 中的 vals 和 vars 在首次实例化 object 时(即首次使用 object 时)进行初始化。

【讨论】:

所有对象都在companionsingleton里面吗? @Khemraj 没有。companion object 是一个单例对象。如果您需要许多不同的单例,您可以为每个单例创建许多对象。我会更新答案。 很好的解释!,我仍然需要一年的时间来理解这种新的语言语法。 这不是最好的答案,因为创建 util 类的最好方法是使用 object 关键字代替 class 关键字 - 那么它是语言级别的单例,我们不必费心创建这样一个类的实例。我们只是像 Java 的静态方法一样使用函数。请看我的回答 单例的初始化怎么样?如果ojbect 不能有构造函数,我们应该使用初始化块(init ...)吗?【参考方案3】:

超级简单的懒惰例子:

companion object 
    val instance: UtilProject by lazy  UtilProject() 

【讨论】:

不需要!只需使用对象关键字 是的。实际上,object 关键字也是如此【参考方案4】:

只需要词对象。

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

而你直接访问只有一个实例的对象

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

【讨论】:

【参考方案5】:

在 Kotlin 中,您应该摆脱实用程序单例类的整个概念。惯用的方法是简单地将所有声明移到顶层。

Java:

public final class Util 
    public static final Util UTIL = new Util();

    private int prefixLength = 4;

    private Util() 

    public void setPrefixLength(int newLen) 
        prefixLength = newLen;
    

    public String extractVin(String input) 
        return input.substring(prefixLength);
    

用法:

String vin = UTIL.extractVin("aoeuVN14134230430")

在 Kotlin 中,只需使用以下内容创建一个名为 util.kt 的单独文件:

var prefixLength = 4

fun String.extractVin() = this.substring(prefixLength)

用法:

val vin = "aoeuVN14134230430".extractVin()

但是……你正在污染***命名空间!

如果您的 Java 直觉在这里触发了危险信号,请记住 是命名空间结构,与 Java 不同,Kotlin 不会将命名空间和封装的问题混为一谈。没有“包私有”访问级别,因此您无需决定某些内容必须保留在同一个包中,以便将其设为包私有。

因此,在 Java 中创建退化类作为解决方法,在 Kotlin 中,您只需在其自己的包中创建一个文件。

【讨论】:

【参考方案6】:
 class TestMySingleton private constructor() 
​
   companion object 
        var single = TestMySingleton()

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


【讨论】:

【参考方案7】:

一个Singleton 示例,经过改造以支持 api 调用。

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
    

【讨论】:

您实现getInstance() 方法的方式不正确。 getInstance() 内部的逻辑表示它将始终返回 non-null 值,但您的返回类型 (Api?) 表示它可以返回 null 值。如果您尝试将返回类型重构为 Api,您将收到 Smart cast to a mutable property is not possible 警告。【参考方案8】:

带参数的变体

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
            
        
    



【讨论】:

一个很好的答案,但它只是整个事情的一部分。这段代码是从这里复制过来的:blog.mindorks.com/how-to-create-a-singleton-class-in-kotlin 看一看就知道全貌了,为什么要这样处理。【参考方案9】:
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 来创建它的实例

【讨论】:

【参考方案10】:

这会有所帮助。我正在使用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
    

【讨论】:

【参考方案11】:

这里的所有答案大部分都是正确的,除非是线程处理。我的用例是这样的

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

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,当从不同线程调用时,它会创建多个改造字段副本。

您还可以重新分配改造客户端以应用额外的静态标头,因为我们使用 "var" 关键字而不是 "val""lateinit var"强>

【讨论】:

以上是关于Kotlin 中的单例类的主要内容,如果未能解决你的问题,请参考以下文章

kotlin 之单例类详解

如何对枚举类型实现的单例模式进行mock

ruby 中的单例类到底是啥?

目标c中的单例类

Ruby 中 BasicObject 的单例类的单例类

请问java 单例类 与 静态类 有何不同?