【问题标题】:Circular references with vals in KotlinKotlin 中带有 val 的循环引用
【发布时间】:2021-06-10 17:31:01
【问题描述】:
在 Kotlin 中,假设我有 data class A (val f: B) 和 data class B (val f: A)。我想初始化本地var a: A 和var b: B 使得a.f 是b 和b.f 是a。 A.f 和 B.f 必须保留为 val。这种循环实例化可行吗?
data class A(val f: B)
data class B(val f: A)
fun foo() {
var a: A
var b: B
// instantiate a and b here with a.f == b and b.f == a ??
}
【问题讨论】:
标签:
immutability
kotlin
circular-reference
【解决方案1】:
如果您将自引用 val 明确声明为 Lazy,则可以这样做:
sealed class MyData {
data class A(val x: Int) : MyData()
data class B(val x : Int, val rb: Lazy<MyData>) : MyData() {
val r: MyData by rb
}
}
fun <A : Any> rec(body: (Lazy<A>) -> A): A {
lateinit var a: A
a = body(lazy { a })
return a
}
fun MyData.print(gas: Int): String = if (gas <= 0) "..." else
when(this) {
is MyData.A -> "A(x=$x)"
is MyData.B -> {
val rbString =
if (rb.isInitialized())
r.print(gas - 1)
else
"<thunk>"
"B(x=$x, rb=$rbString)"
}
}
fun main() {
val a = MyData.A(42)
val b1 = MyData.B(1, lazy { a })
println(b1.r) // Force value
println(b1)
val b2 = rec<MyData.B> { b2 -> MyData.B(1, b2) }
println(b2.r.print(4))
}
打印出来
A(x=42)
B(x=1, rb=A(x=42))
B(x=1, rb=B(x=1, rb=B(x=1, rb=B(x=1, rb=...))))
【解决方案2】:
传递一个尚未构造的对象作为参数似乎是不可能的。所以,我认为原始数据类不可能进行这种交叉引用初始化。
但是可以做一些解决方法:
data class A(val f: A.() -> B)
data class B(val f: B.() -> A)
val A.b: B get() = f(this)
val B.a: A get() = f(this)
fun test() {
val O = object {
val refA: A = A { refB }
val refB: B = B { refA }
}
var a = O.refA
var b = O.refB
// validating cross-refs
require( a === b.a )
require( b === a.b )
require( b === b.a.b )
require( a === a.b.a )
println("Done.")
}
【解决方案3】:
不完全是你想要的,但应该可以工作:
interface A {
val f: B
}
interface B {
val f: A
}
data class AImpl(override var f: B) : A
data class BImpl(override var f: A) : B
fun <T> uninitialized(): T = null as T
fun foo() {
var aImpl = AImpl(uninitialized())
var bImpl = BImpl(aImpl)
aImpl.f = bImpl
val a: A = aImpl
val b: B = bImpl
}
如果你不关心数据类属性是vals,你可以去掉接口,只使用实现类。
【解决方案4】:
由 kotlinlang Slack 小组的 Dmitry Jemerov 回答:
唯一的可能是使用反射。将一个虚拟的B实例传递给A的构造函数,然后通过A实例作为参数创建一个真实的B实例,然后使用反射将A实例中的“f”字段的值更改为B实例。
但我强烈建议您不要这样做,而是重新考虑您的数据模型。