假设我有一个类似sealed class的层次结构:

sealed class A {
    abstract val x: Int
    abstract fun copyX(x1: Int): A
}

data class A1(override val x: Int, val s1: String) : A() {
    override fun copyX(x1: Int): A {
        return this.copy(x = x1)
    }
}

data class A2(override val x: Int, val s2: String) : A() {
    override fun copyX(x1: Int): A {
        return this.copy(x = x1)
    }
}

所有数据类都具有x字段,并应提供copyX(x1: Int)方法来复制除x之外的所有字段,并使用x覆盖x1。例如,
fun foo(a: A): A { a.copyX(100) }

上面的定义可能有用,但是在所有数据类上重复copyX似乎很笨拙。您如何建议摆脱这种重复的copyX

最佳答案

首先,您可以将copyX实现为扩展(甚至是A的成员),以便将代码集中在一个位置,并避免至少在密封的类子类型中复制copyX函数:

sealed class A {
    abstract val x: Int
}

fun A.copyX(x1: Int): A = when (this) {
    is A1 -> copy(x = x1)
    is A2 -> copy(x = x1)
}

data class A1(override val x: Int, val s1: String) : A()

data class A2(override val x: Int, val s2: String) : A()

如果您有很多密封的子类型,并且所有子类型都是data类或具有copy函数,则也可以使用反射来通用地复制它们。为此,您需要从primaryConstructor中获取 copy 或名为KClass的函数,然后填充调用的参数,按名称查找x参数,并为其放置x1值,并放置从component1()component2()获得的值等调用或保留其他参数的默认值。它看起来像这样:
fun A.copyX(x1: Int): A {
    val copyFunction = this::class.memberFunctions.single { it.name == "copy" }
    val args = mapOf(
        copyFunction.instanceParameter!! to this,
        copyFunction.parameters.single { it.name == "x" } to x1
    )
    return copyFunction.callBy(args) as A
}

这是可行的,因为callBy允许省略可选参数。

请注意,它需要对kotlin-reflect的依赖,并且仅适用于Kotlin/JVM。同样,反射也有一些性能开销,因此它不适用于对性能有严格要求的代码。您可以使用Java反射(this::class.javagetMethod(...))代替(这会更冗长)并缓存反射实体来对此进行优化。

10-06 07:00