组合/合并Kotlin中的数据类

组合/合并Kotlin中的数据类,kotlin,kotlin-reflect,Kotlin,Kotlin Reflect,是否有方法在不指定所有属性的情况下合并kotlin数据类 data class MyDataClass(val prop1: String, val prop2: Int, ...//many props) 具有以下签名的函数: fun merge(left: MyDataClass, right: MyDataClass): MyDataClass 如果此函数检查两个类上的每个属性,如果它们不同,则使用left参数创建一个新的MyDataClass 这是否可能使用kotlin reflec

是否有方法在不指定所有属性的情况下合并kotlin数据类

data class MyDataClass(val prop1: String, val prop2: Int, ...//many props)
具有以下签名的函数:

fun merge(left: MyDataClass, right: MyDataClass): MyDataClass
如果此函数检查两个类上的每个属性,如果它们不同,则使用left参数创建一个新的MyDataClass

这是否可能使用kotlin reflect或其他方法

编辑:更清晰

下面是我希望能够做的更好的描述

  data class Bob(
        val name: String?,
        val age: Int?,
        val remoteId: String?,
        val id: String)

@Test
fun bob(){

    val original = Bob(id = "local_id", name = null, age = null, remoteId = null)
    val withName = original.copy(name = "Ben")
    val withAge = original.copy(age = 1)
    val withRemoteId = original.copy(remoteId = "remote_id")

    //TODO: merge without accessing all properties
    // val result = 
    assertThat(result).isEqualTo(Bob(id = "local_id", name = "Ben", age=1, remoteId = "remote_id"))
}

您的要求与复制
值完全相同:

fun merge(left: MyDataClass, right: MyDataClass) = left.copy()
也许一种用法不能正确理解另一种用法。如果这不是你想要的,请详细说明

请注意,由于未使用
right
,因此可以将其设置为vararg并根据需要“合并”:

编辑

Kotlin中的类不跟踪它们的增量。想想你在这个过程中得到了什么。在你第一次改变之后

current = Bob("Ben", null, null, "local_id")
next = Bob(null, 1, null, "local_id")
如何知道您希望
next
将更改应用于
age
,而不是
name
?如果您只是基于可空性进行更新,
@姆弗顿有一个很好的答案。否则,您需要自己提供信息。

如果要在左侧的值为
null时从右侧复制值,则可以执行以下操作:

inline infix fun <reified T : Any> T.merge(other: T): T {
    val propertiesByName = T::class.declaredMemberProperties.associateBy { it.name }
    val primaryConstructor = T::class.primaryConstructor
        ?: throw IllegalArgumentException("merge type must have a primary constructor")
    val args = primaryConstructor.parameters.associateWith { parameter ->
        val property = propertiesByName[parameter.name]
            ?: throw IllegalStateException("no declared member property found with name '${parameter.name}'")
        (property.get(this) ?: property.get(other))
    }
    return primaryConstructor.callBy(args)
}

当我们可以定义要组合的字段时,组合数据类的一种特定于类的方法是:

data class SomeData(val dataA: Int?, val dataB: String?, val dataC: Boolean?) {
    fun combine(newData: SomeData): SomeData {        
        //Let values of new data replace corresponding values of this instance, otherwise fall back on the current values.
        return this.copy(dataA = newData.dataA ?: dataA,
                dataB = newData.dataB ?: dataB,
                dataC = newData.dataC ?: dataC)
    }
}

@mfulton26的解决方案只合并作为主构造函数一部分的属性。我已经扩展到支持所有属性

inline infix fun <reified T : Any> T.merge(other: T): T {
    val nameToProperty = T::class.declaredMemberProperties.associateBy { it.name }
    val primaryConstructor = T::class.primaryConstructor!!
    val args = primaryConstructor.parameters.associate { parameter ->
        val property = nameToProperty[parameter.name]!!
        parameter to (property.get(other) ?: property.get(this))
    }
    val mergedObject = primaryConstructor.callBy(args)
    nameToProperty.values.forEach { it ->
        run {
            val property = it as KMutableProperty<*>
            val value = property.javaGetter!!.invoke(other) ?: property.javaGetter!!.invoke(this)
            property.javaSetter!!.invoke(mergedObject, value)
        }
    }
    return mergedObject
}
inline infix fun T.merge(其他:T):T{
val nameToProperty=T::class.declaredMemberProperties.associateBy{it.name}
val primaryConstructor=T::class.primaryConstructor!!
val args=primaryConstructor.parameters.associate{parameter->
val property=nametroperty[参数.名称]!!
(property.get(其他)的参数:property.get(此))
}
val mergedObject=primaryConstructor.callBy(args)
nameToProperty.values.forEach{it->
跑{
val property=将其作为KMutableProperty
val value=property.javaGetter!!.invoke(其他)?:property.javaGetter!!.invoke(此)
property.javaSetter!!.invoke(合并对象,值)
}
}
返回合并对象
}

因此,在新实例中,您希望保持
left
right
中相同的属性值,而对于不相同的属性值,您希望使用
left
中的属性值?@zsmb13是正确的这与复制
left
有什么不同吗?这真的很好,但是在以下情况下,不能作为获取非法Aruament异常来工作:
数据类Bob(val name:String?,val age:Int?,val id:String)
,因为memberProperties返回的顺序或age,id name对于构造函数来说是错误的顺序,因此在构造中会崩溃<代码>infix fun T.merge(映射:KProperty1.->Any?:T{val kClass=this::class as kClass val constructorParameters=kClass.primaryConstructor!!.parameters val props=kClass.memberProperties val merged:Array=constructorParameters.map{cParam->props.find{it.name==cParam.name}?.mapping()}.toTypedArray()返回此::class.primaryConstructor!!.call(*merged);}
这似乎解决了我的问题。尽管不确定这作为一个解决方案在性能方面是否可行@holi-java@BenFlowers先生,现在怎么样?谢谢你。我还必须在declaredMemberProperties上添加.filter{order.containsKey(it.name)},因为我有一个属性不是构造函数的一部分。这样做的目的是在一个函数中使用,其中对一个数据对象应用了许多操作。其中,操作返回对象的副本,其中包含一些更新的字段。让我们假设'val a=update1(dataclass)`
val b=update2(dataclass)
,其中每个函数并行执行。现在我需要合并dataclass、a和b的值,在这里我想做一些类似于
listOf(a,b).reduce(dataclass){acc,i->merge(i,acc)}
这也可以用于非数据类,如果主构造函数包含要合并的字段。@abhimuktheeswar此解决方案确实依赖于反射,因此proguard在代码中找不到对其中一些成员的任何静态引用,您需要找到哪些成员不可用,然后指示proguard不要删除这些成员。也许我应该正确地进行注释。它不起作用(NullPointerException)——至少在我启用r8的情况下是这样。移除可空变量的强制展开后&将返回类型设置为可空。它起作用了。那些强行打开的东西很危险。绑定到空指针exceptions@Inn0vative1你能详细说明一下吗?我猜你指的是
。一个
数据类
将始终有一个
主构造函数
,并且代码只按其名称查找属性,因此我认为这两个
是“安全的”。当然,可能有更好的方法来编写这篇文章,尽管现在可能也利用了Kotlin最近添加的内容。这段代码不是marge类中的marge类。我希望这能对我们称之为深度课程的学生起作用?数据类Some(val a:String?、val b:String?)数据类Other(val Some:Some?、val c:String?、val d:String?)当我执行其他操作时(Some(“a”,null),“c”,null)。合并(Some(Some(Some(null,“b”),null,“d)),我希望得到其他(Some(“a”,“b”),“c”,“d”)
current = Bob("Ben", null, null, "local_id")
next = Bob(null, 1, null, "local_id")
inline infix fun <reified T : Any> T.merge(other: T): T {
    val propertiesByName = T::class.declaredMemberProperties.associateBy { it.name }
    val primaryConstructor = T::class.primaryConstructor
        ?: throw IllegalArgumentException("merge type must have a primary constructor")
    val args = primaryConstructor.parameters.associateWith { parameter ->
        val property = propertiesByName[parameter.name]
            ?: throw IllegalStateException("no declared member property found with name '${parameter.name}'")
        (property.get(this) ?: property.get(other))
    }
    return primaryConstructor.callBy(args)
}
data class MyDataClass(val prop1: String?, val prop2: Int?)
val a = MyDataClass(null, 1)
val b = MyDataClass("b", 2)
val c = a merge b // MyDataClass(prop1=b, prop2=1)
data class SomeData(val dataA: Int?, val dataB: String?, val dataC: Boolean?) {
    fun combine(newData: SomeData): SomeData {        
        //Let values of new data replace corresponding values of this instance, otherwise fall back on the current values.
        return this.copy(dataA = newData.dataA ?: dataA,
                dataB = newData.dataB ?: dataB,
                dataC = newData.dataC ?: dataC)
    }
}
inline infix fun <reified T : Any> T.merge(other: T): T {
    val nameToProperty = T::class.declaredMemberProperties.associateBy { it.name }
    val primaryConstructor = T::class.primaryConstructor!!
    val args = primaryConstructor.parameters.associate { parameter ->
        val property = nameToProperty[parameter.name]!!
        parameter to (property.get(other) ?: property.get(this))
    }
    val mergedObject = primaryConstructor.callBy(args)
    nameToProperty.values.forEach { it ->
        run {
            val property = it as KMutableProperty<*>
            val value = property.javaGetter!!.invoke(other) ?: property.javaGetter!!.invoke(this)
            property.javaSetter!!.invoke(mergedObject, value)
        }
    }
    return mergedObject
}