在Kotlin中处理循环引用问题的正确方法

huangapple go评论60阅读模式
英文:

Correct way to handle circular reference problem in kotlin

问题

以下是您要翻译的代码部分:

我正在尝试构建一个简单的 JSON 解析器下面是我的 JSON 类型表示使用了密封类

sealed interface JValue {
    data class JString(val value: String): JValue
    data class JNumber(val value: Float): JValue
    data class JBool(val value: Boolean): JValue
    data object JNull: JValue
    data class JObject(val value: Map<String, JValue>): JValue
    data class JArray(val value: List<JValue>): JValue
}

val jValueArr: JValue = getJArrayValue()
val jValueString: JValue = JValue.JString("Test")
val jValueNumber: JValue = JValue.JNumber(100f)

val jArray = JValue.JArray(listOf(jValueString, jValueNumber, jValueArr))

fun getJArrayValue(): JValue.JArray = JValue.JArray(listOf(jValueArr))

fun main() {
    println(jArray.value)
}

我明白问题出在 JObjectJArray,因为它们可以持有一个 JValue(循环依赖?)

如果我运行上述代码,它会打印以下值作为 jArray

[JString(value=Test), JNumber(value=100.0), JArray(value=[null])]

正如您所看到的,第三个元素是 JArray(value=[null]) 而不是 jValueArr,但我可以理解这是因为 Kotlin 惰性地初始化对象,并且在调用 getJArrayValue()jValueArr 尚未初始化。

我的问题是,我如何正确处理这种情况?

链接到我的实际代码 - https://github.com/VenkateswaranJ/ParserCombinatorsKT/blob/master/src/main/kotlin/JsonParser.kt#L98-L130

更新:

我在解析器中通过一些虚拟的前向引用来修复了它(不确定这是否是最佳方法)
这是代码 - https://github.com/VenkateswaranJ/ParserCombinatorsKT/blob/master/src/main/kotlin/JsonParser.kt#L16-L26

英文:

I'm trying to build a simple json parser and below is my JSON type representation with a sealed class

sealed interface JValue {
    data class JString(val value: String): JValue
    data class JNumber(val value: Float): JValue
    data class JBool(val value: Boolean): JValue
    data object JNull: JValue
    data class JObject(val value: Map<String, JValue>): JValue
    data class JArray(val value: List<JValue>): JValue
}

val jValueArr: JValue = getJArrayValue()
val jValueString: JValue = JValue.JString("Test")
val jValueNumber: JValue = JValue.JNumber(100f)

val jArray = JValue.JArray(listOf(jValueString, jValueNumber, jValueArr))

fun getJArrayValue(): JValue.JArray = JValue.JArray(listOf(jValueArr))

fun main() {
    println(jArray.value)
}

The problematic parts are JObject and JArray as they can hold a JValue (circular dependency?)

If I run the above code it prints the below value as jArray

[JString(value=Test), JNumber(value=100.0), JArray(value=[null])]

as you see the third element is JArray(value=[null]) instead of jValueArr, but I can understand that it's null because the kotlin initializes the objects lazy and jValueArr won't be initialized while calling getJArrayValue().

My question is, how can I properly handle this scenario?

Link to my actual code - https://github.com/VenkateswaranJ/ParserCombinatorsKT/blob/master/src/main/kotlin/JsonParser.kt#L98-L130

Update:

I fixed it in my parser with some dummy forward reference (not sure it's the best way of doing it)
Here is the code - https://github.com/VenkateswaranJ/ParserCombinatorsKT/blob/master/src/main/kotlin/JsonParser.kt#L16-L26

答案1

得分: 1

在大多数情况下,不可能存在双向都是只读的循环引用。我们必须至少使一侧可变。

在您的特定情况下,我们有一个列表,因此可以最初创建一个可变列表,然后将其设置为只读:

val jValueArr: JValue = run {
    val list = mutableListOf<JValue>()
    JValue.JArray(list).also { list += it }
}

上述解决方案可能被认为不是完全安全的,因为我们可以将列表转回MutableList并对其进行修改。或者,我们可以创建自己的MutableList实现,不允许在将其切换为只读后进行修改。或者,我们可以使用 buildList() ,它已经提供了这样的实现。

请注意,您不能打印具有循环引用的数据类,因为这样会导致无限递归。此外,在与JSON相关的数据结构中使用循环引用似乎很奇怪。JSON 不支持循环引用。

英文:

In most cases it is not possible to have a circular reference where both sides are read-only. We have to make at least one side mutable.

In your specific case we have a list, so we can initially create a mutable list and then make it read-only:

val jValueArr: JValue = run {
	val list = mutableListOf&lt;JValue&gt;()
	JValue.JArray(list).also { list += it }
}

Above solution may be considered not exactly safe, as we can cast the list back to MutableList and mutate it. Alternatively, we can create our own implementation of MutableList which doesn't allow mutating after switching it to read-only. Or we can use buildList() which provides such implementation already.

Please be aware you can't print data classes with circular references, because then you get into an infinite recursion. Also, it feels strange to use circular references in a data structure which seems related to JSON. JSON doesn't support circular references.

答案2

得分: 0

你可以通过延迟初始化jValueArr来解决循环初始化问题:

val jValueArr by lazy { getJArrayValue() }

除非你声明变量为懒加载,否则它会按声明顺序进行初始化。

然而,在你当前的设计中,仍然存在循环依赖问题。你的getJArrayValue函数会创建一个包含jValueArr本身的列表的JArray。这可能导致无限递归,因为它本质上是一个循环结构,jValueArr包含它本身,而它包含它本身,依此类推。

你应该重新设计你的数据结构以避免无限递归。

英文:

You can fix the circular initialization problem with lazy initialization of jValueArr:

val jValueArr by lazy { getJArrayValue() } 

unless you declare your variable lazy, it is initialized in the order of declaration.

Yet, you will still have a circular dependency issue, in your current design, your getJArrayValue function is creating a JArray with a list that contains jValueArr itself. This can lead to infinite recursion, as it's essentially a circular structure where jValueArr contains itself, which contains itself, and so on.

You should redesign your data to avoid endless recursion.

huangapple
  • 本文由 发表于 2023年7月11日 05:36:30
  • 转载请务必保留本文链接:https://go.coder-hub.com/76657489.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定