【问题标题】: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: Avar b: B 使得a.fbb.faA.fB.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实例。

          但我强烈建议您不要这样做,而是重新考虑您的数据模型。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2019-04-19
            • 1970-01-01
            • 1970-01-01
            • 2022-09-29
            • 1970-01-01
            • 2017-01-02
            • 1970-01-01
            相关资源
            最近更新 更多