Search code examples
kotlinconstructor

Kotlin - Pass "this" as a constructor argument


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?


Solution

  • 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.