I'm writing a binary tree class. I want to avoid null
values, and I decided to use a designated value nil
. So, when an element doesn't have a left child, instead of n.left = null
I have n.left = nil
. But, I need to specify the left and the right child for nil
, and the only choice is nil
itself. So, I want to write this:
class Node(val left: Node, val right: Node) {
companion object {
val nil = Node(nil, nil)
}
}
or at most:
class Node(val left: Node, val right: Node) {
private constructor(): this(this, this)
companion object {
val nil = Node()
}
}
Those don't compile, of course. This is what I'm forced to write:
class Node {
val left: Node
val right: Node
constructor(left: Node, right: Node) {
this.left = left
this.right = right
}
private constructor() {
this.left = this
this.right = this
}
companion object {
val nil = Node()
}
}
It has much more code, especially after I'll add more fields. Any way to write this concisely?
Disclaimer: I would reconsider your avoidance of null. Nullability is built into Kotlin's type system, and null is a good representation of "no value". Plus, Kotlin has the safe call (?.
) and elvis (?:
) operators and has smart casting, which works with checks for null.
If you want to avoid nullable types, then perhaps something like this would work for you:
sealed interface Node {
val left: Node
val right: Node
}
object EmptyNode : Node {
override val left: EmptyNode get() = this
override val right: EmptyNode get() = this
}
class RegularNode(
override val left: Node = EmptyNode,
override val right: Node = EmptyNode,
) : Node
But it will require you to update two implementations for every property and function you add to the Node
interface. Though you could make use of extension functions where possible.
That said, I assume you'll be adding a value
property to Node
. And given the nature of your use case, this property will have to be generic. That makes things more difficult, though you could try something like this:
val Node<*>.isEmptyNode: Boolean
get() = this is EmptyNode<*>
sealed interface Node<T> {
val left: Node<T>
val right: Node<T>
val value: T
}
class EmptyNode<T> : Node<T> {
override val left: EmptyNode<T> get() = this
override val right: EmptyNode<T> get() = this
override val value: Nothing
get() = throw IllegalStateException("EmptyNode has no value")
}
class RegularNode<T>(
override val value: T,
override val left: Node<T> = EmptyNode(),
override val right: Node<T> = EmptyNode(),
) : Node<T>
But note now EmptyNode
is not a singleton.