【问题标题】:Singleton class in KotlinKotlin 中的单例类
【发布时间】: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,但它是带参数的,如果没有参数,我无法将其转换。

【问题讨论】:

    标签: android kotlin


    【解决方案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
                      }
                  }
              }
          }
          
          
          

          【讨论】:

          【解决方案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"强>

              【讨论】:

                猜你喜欢
                • 2020-12-25
                • 1970-01-01
                • 1970-01-01
                • 2019-11-11
                • 2019-10-09
                • 2017-03-16
                • 1970-01-01
                • 2020-08-26
                • 1970-01-01
                相关资源
                最近更新 更多