【问题标题】:How to clone object in Kotlin?如何在 Kotlin 中克隆对象?
【发布时间】:2018-08-09 17:48:31
【问题描述】:

Kotlin documentation 仅在访问 Java 和枚举类中描述了克隆。在后一种情况下,克隆只是抛出异常。

那么,我/应该如何克隆任意 Kotlin 对象?

我应该像在 Java 中一样使用 clone() 吗?

【问题讨论】:

    标签: kotlin clone


    【解决方案1】:

    对于data class,您可以使用编译器生成的copy() method。请注意,它将执行浅拷贝。

    要创建集合的副本,请使用toList()toSet() 方法,具体取决于您需要的集合类型。这些方法总是创建一个集合的新副本;他们还执行浅拷贝。

    对于其他类,没有特定于 Kotlin 的克隆解决方案。如果它符合您的要求,您可以使用.clone(),如果不符合您的要求,您可以构建一个不同的解决方案。

    【讨论】:

    • 如何克隆列表?例如List<String>
    • @Dims,您可以通过在 Iterable toList() 或 toMutableList() 上使用扩展来做到这一点。
    • @yole:是否记录/保证toSet() 返回一个新实例?这似乎是它当前的实现方式,但library docs 只说它返回一个集合。如果它不是图书馆合同的一部分,我有点犹豫是否要依赖这种行为。
    【解决方案2】:

    您可以使用Gson library 将原始对象转换为字符串,然后将该字符串转换回实际的对象类型,您将获得一个克隆。虽然这不是 Gson 库的预期用途,它实际上用于在 JSON 和其他对象类型之间进行转换,但我设计了这种方法来解决我的许多基于 Kotlin 的 Android 应用程序中的克隆问题。 看我的例子。将此函数放在要创建克隆的类/模型中。在我的示例中,我正在克隆一个 Animal 类型的对象,所以我将把它放在 Animal 类中

    class Animal{
     fun clone(): Animal 
     {
       val stringAnimal = Gson().toJson(this, Animal::class.java)
       return Gson().fromJson<Animal>(stringAnimal, Animal::class.java)
     }
    }
           
    

    然后像这样使用它:

    val originalAnimal = Animal()
    val clonedAnimal = originalAnimal.clone()
    

    【讨论】:

    • 这不是一个漂亮的方法,但它是唯一适合我的方法。
    • @OsvelAlvarezJacomino 这适用于您的大多数用例
    • 我知道它适用于大多数情况,但我不喜欢为了避免浅拷贝而添加外部库。就我而言,我需要将 Gson 用于代码中的其他事情,这就是我决定使用它的原因。
    • 没错。在我们在 Kotlin 中找到更好的内置解决方案之前,这将作为一种解决方法
    • @PauloMerson 你可以在这里找到最近的性能测试:baeldung.com/java-performance-mapping-frameworks; gson 的速度大约是该测试中表现最慢的程序的两倍,因为必须同时进行序列化和反序列化。是的,开销可能会慢 100 倍!
    【解决方案3】:

    使用.copy() 可以轻松克隆 Kotlin data class

    所有浅拷贝的值,请务必小心处理任何列表/数组内容。

    .copy() 的一个有用功能是能够在复制时更改任何值。使用此类:

    data class MyData(
        val count: Int,
        val peanuts: Int?,
        val name: String
    )
    val data = MyData(1, null, "Monkey")
    

    您可以为任何属性设置值

    val copy = data.copy(peanuts = 100, name = "Elephant")
    

    copy 中的结果将具有值 (1, 100, "Elephant")

    【讨论】:

      【解决方案4】:

      如果您尝试克隆的类没有实现Cloneable 或者不是数据类并且是外部库的一部分,您可以创建一个返回新实例的扩展方法。例如:

      class Person {
        var id: String? = null
        var name: String? = null
      } 
      fun Person.clone(): Person {
        val person = Person()
        person.id = id
        person.name = name
        return person 
      }
      

      【讨论】:

        【解决方案5】:

        它需要为您的类实现 Cloneable,然后将 clone() 覆盖为公共,例如:

        public override fun clone(): Any {<your_clone_code>}
        

        https://discuss.kotlinlang.org/t/how-to-use-cloneable/2364/3

        【讨论】:

          【解决方案6】:
          fun <T : Any> clone (obj: T): T {
            if (!obj::class.isData) {
              println(obj)
              throw Error("clone is only supported for data classes")
            }
          
            val copy = obj::class.memberFunctions.first { it.name == "copy" }
            val instanceParam = copy.instanceParameter!!
            return copy.callBy(mapOf(
              instanceParam to obj
            )) as T
          }
          
          

          【讨论】:

          • 我喜欢这种方法,但它似乎无法编译。我猜 Kotlin 反射 API 发生了一些变化?我将这个逐字复制粘贴到我的代码中,导致编译错误:Unresolved reference: memberFunctions
          • 看起来您必须将org.jetbrains.kotlin:kotlin-reflect 添加到您的依赖项中才能使用反射。但是,我试过了,我仍然得到同样的错误。我不确定是我做错了什么还是上游出了什么问题。
          【解决方案7】:

          我投票给@yole 以获得好的答案,但如果您不(或不能)使用数据类,还有其他方式。您可以像这样编写辅助方法:

          object ModelHelper {
          
              inline fun <reified T : Serializable> mergeFields(from: T, to: T) {
                  from::class.java.declaredFields.forEach { field ->
                      val isLocked = field.isAccessible
                      field.isAccessible = true
                      field.set(to, field.get(from))
                      field.isAccessible = isLocked
                  }
              }
          
          }
          

          因此您可以通过以下方式将实例 A“复制”到 B 中:

          val bInstance = AClassType()
          ModelHelper.mergeFields(aInstance, bInstance)
          

          有时,我使用这种方式将来自多个实例的数据合并到一个对象中,该对象的值可用(非空)。

          【讨论】:

          • 是的,有时我们不能使用data classes,如果将它们设为abstractopen。你怎么看,这段代码会不会在Androidrelease build 中编译(它不喜欢反射)。
          • 这很好,但有一个错误。如果from 中的字段是null,则不会将其复制到to(因此保留原始值,可能不是null)。解决方法是删除if
          • @acdcjunior 是的,你是对的。我只是删除了 if 语句。我使用 if 语句是因为我的队友中有 API 问题,有时没有响应数据有时有数据,所以如果有数据,我会捕获所有数据并保存到本地数据库中,如果没有,null 将覆盖数据?
          • 有道理!感谢您的修复!
          • @CoolMind 我申请了公司项目,Playstore 上有可用的应用程序。但我认为这种方式不推荐,因为反射。
          【解决方案8】:

          这是适用于任何对象类型的一致解决方案:

          Kotlin 的 Array 数据结构提供了一个clone() 方法,可以用来克隆数组的内容:

          val a = arrayOf(1)
          //Prints one object reference
          println(a)     
          //Prints a different object reference
          println(a.clone())
          

          从 Kotlin 1.3 开始,所有主要目标都支持 clone 方法,因此它应该可以跨平台使用。

          【讨论】:

            【解决方案9】:

            也可以使用kotlinx.serialization克隆一个对象

            import kotlinx.serialization.Serializable
            import kotlinx.serialization.json.Json
            import kotlinx.serialization.json.JsonConfiguration
            
            @Serializable
            class A
            {
                val name: String = "Cloneable class A"
            
                fun clone(): A {
                    val json = Json(JsonConfiguration.Stable)
                    val jsonStr = json.stringify(serializer(), this)
                    return json.parse(serializer(), jsonStr)
                }
            }
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2018-11-27
              • 2011-09-19
              • 1970-01-01
              • 1970-01-01
              • 2010-11-08
              • 2012-11-17
              • 2010-09-07
              • 2012-12-07
              相关资源
              最近更新 更多