我有以下类结构,代表简单的树。每个项目可以有多个子项和父项。
树根让我头疼。我试图在不使用null
的情况下执行此操作,因此我可以通过调用item.parent
来向上遍历树。为了简化它,我希望root将自己作为父级,但我无法弄清楚如何做到这一点。
interface Item {
val parent: Directory
}
interface ItemWithChildren{
val children: MutableList<Item>
}
class Directory() : Item, ItemWithChildren {
override val children: MutableList<Item> = mutableListOf()
override val parent: Directory by lazy { this }
constructor(par: Directory) : this() {
parent = par //Error: val cannot be reassigned
}
}
class File(override val parent: Directory) : Item
该代码无法编译,因为无法重新分配val parent
。但是使用this
作为默认参数值也是不可能的。有什么出路吗?
如果我允许父级可以为空,那么解决方案很简单。但如果可能的话,我不想使用空值。此外null
会击败item.parent
链。
答案 0 :(得分:3)
您可以使用init
块。 e.g:
class Directory(parent: Directory? = null) : Item, ItemWithChildren {
override val children: MutableList<Item> = mutableListOf()
override val parent: Directory
init {
this.parent = parent ?: this
}
}
或者,您可以为“root”创建单独的“父”实现。 e.g:
interface ChildItem /* renamed from `Item` for clarity */ {
val parent: ParentItem
}
interface ParentItem /* renamed from `ItemWithChildren` for clarity */ {
val children: MutableList<ChildItem>
}
class Root() : ParentItem {
override val children: MutableList<ChildItem> = mutableListOf()
}
class Directory(override val parent: ParentItem) : ChildItem, ParentItem {
override val children: MutableList<ChildItem> = mutableListOf()
}
class File(override val parent: ParentItem) : ChildItem
这样,您的“root”项目没有parent
属性,类似于“leaf”(“file”)项目没有children
属性的方式。您可能还希望使ChildItem
和ParentItem
接口扩展一个公共接口(例如,名为Item
)。
答案 1 :(得分:1)
@ mfulton26以严格要求的方式回答了如何做到这一点。但对于其他可能对这种选择感到疑惑的人,他们仍然应该考虑在Kotlin中这类工作的null
值。
您可以拥有null
属性和一些派生属性,这些属性允许将访问权限声明为null
。因为无论哪种方式(您计划避免null
或接受和使用null
),您都不得不问“我有父母吗?”这几乎是一样的问“是父null吗?”那么为什么一个不常见的“可能无限循环导致”解决这种情况呢?
如果我的树类类似于:
data class Something(val text: String, val parentOrNull: Something? = null) {
val parent: Something get() = parentOrNull!!
val hasParent = parentOrNull != null
}
然后,我可以选择如何访问父级,无需担心null
:
val root = Something("rooty")
val child = Something("youngun", root)
val leaf = Something("baby", child)
fun printPathToNode(node: Something) {
// use derived properties to check and not worry about the null
if (node.hasParent) printPathToNode(node.parent)
println(node)
}
fun findRoot(node: Something): Something {
// use null operators to not worry about the null
return node.parentOrNull?.let { findRoot(it) } ?: node
}
然后你可以看到它运行良好且输出良好,null
没有问题:
printPathToNode(leaf) // rooty, youngun, baby
printPathToNode(child) // rooty, youngun
printPathToNode(root) // rooty
println(findRoot(leaf)) // rooty
println(findRoot(child)) // rooty
println(findRoot(root)) // rooty
在没有意义的情况下应该避免无效。但有时它们实际上是一个合理的选择。 Kotlin有助于保护你,当你有可空的价值时,通过了解它们而不只是假装一切都没问题。然后它会给你很好的nullability operators来帮助你与他们合作。
答案 2 :(得分:1)
我将如何使用@ mfulton的答案:
class Directory(parent: Directory? = null) : Item, ItemWithChildren {
override val children = mutableListOf<Item>()
override val parent = parent ?: this
}