Kotlin 中带有 val 的循环引用

Circular references with vals in Kotlin

在 Kotlin 中,假设我有 data class A (val f: B)data class B (val f: A)。我想初始化本地 var a: Avar b: B,这样 a.f 就是 bb.f 就是 aA.fB.f 必须保持有效。这种循环实例化可行吗?

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 ??
}

Dmitry Jemerov 在 kotlinlang Slack 组中的回答:

The only possibility is to use reflection. Pass a dummy B instance to the constructor of A, then create a real instance of B passing the A instance as a parameter, and then use reflection to change the value of “f” field in the A instance to the B instance.

But I would strongly suggest you to not do that, and instead to reconsider your data model.

不完全是你想要的,但应该有效:

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
}

如果您不关心数据 class 属性是 val,您可以摆脱接口并只使用实现 classes.

传递一个尚未构造的对象作为参数似乎是不可能的。所以,我认为原始数据 类 不可能进行这种交叉引用初始化。

但是可以采取一些解决方法:

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.")
}

如果您将自引用 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=...))))