简体   繁体   English

Kotlin 中带有 val 的循环引用

[英]Circular references with vals in Kotlin

In Kotlin, say I have data class A (val f: B) and data class B (val f: A) .在 Kotlin 中,假设我有data class A (val f: B)data class B (val f: A) I want to initialize local var a: A and var b: B such that af is b and bf is a .我想初始化局部var a: Avar b: B使得afb并且bfa Af and Bf must remain vals. AfBf必须保持为 vals。 Is this circular instantiation possible?这种循环实例化可能吗?

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

Not exactly what you want but should work:不完全是你想要的,但应该工作:

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
}

If you do not care about data class properties being val s, you can get rid of interfaces and use just implementation classes.如果您不关心数据类属性是val ,则可以摆脱接口并仅使用实现类。

Answered by Dmitry Jemerov on the kotlinlang Slack group: 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.将一个虚拟的B实例传递给A的构造函数,然后创建一个B的真实实例,将A实例作为参数传递,然后使用反射将A实例中“f”字段的值更改为B实例。

But I would strongly suggest you to not do that, and instead to reconsider your data model.但我强烈建议您不要这样做,而是重新考虑您的数据模型。

Passing an object which is not constructed yet as argument seems to be impossible.传递一个尚未构造的对象作为参数似乎是不可能的。 So, I think such cross-reference initialization is not possible with original data classes.所以,我认为这种交叉引用初始化对于原始数据类是不可能的。

But some workaround could be done though:但是可以做一些解决方法:

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

You can do it if you explicitly declare your self-referential val as Lazy :如果您将自引用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))
}

This prints这打印

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=...))))

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM