Kotlin 中带有 val 的循环引用
Circular references with vals in Kotlin
在 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
必须保持有效。这种循环实例化可行吗?
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=...))))
在 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
必须保持有效。这种循环实例化可行吗?
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=...))))