Why outer class cannot see private members of inner class?

What was the reason to diverge from Java in this point? I can’t seem to find the rationale in the docs or on this site.

1 Like

Probably because the JVM actually can’t and Java generates appropriate synthetic accessors. Kotlin just tries to limit the idea in the first place.

1 Like

Wow, I didn’t know that. Still, why can’t Kotlin do the same? It isn’t that language designers shy away from generating bytecode :slight_smile:

Suppose there’s a tree type and a tree node type. You want to allow modifying node pointers only from the tree code, because it preserves some invariants. In Java, you can simply make the Node a (static) inner class of Tree, make the pointer fields private, have a public getter and then either use a private setter or simply manipulate the pointer from the Tree class. Seems like there’s no good way to do that in Kotlin.

class Tree {
  private val root = Node()

  fun move(node: Node, after: Node) {
    // Need to manipulate pointers while maintaining invariants.
    // E.g. need to say after.nextSibling = node, but how?
  }

  class Node(
    parent: Node? = null,
    firstChild: Node? = null,
    nextSibling: Node? = null
  ) {
    var parent: Node? = parent; private set
    var firstChild: Node? = firstChild; private set
    var nextSibling: Node? = nextSibling; private set
  }
}

In most cases you can get away with making the type itself private. In your case you’ll have to go the extra way by having a Node interface and a NodeImpl class (which is private). Btw. trees are typically implemented immutable (but indeed don’t have to).

Just declare your node impl as a private top level class in the same file.

1 Like

The point is that in a tree you would want your node to be public but have members which can only be accessed by the tree itself, so your node can not be just private

The solution (admittedly not quite perfect) is to have the following:

class Tree {
  val root: Node = NodeImpl

  fun move(node: Node, after: Node) { /* some impl */ }

  interface Node {
    val parent: Node?
    val firstChild: Node?
    val nextSibling: Node?
  }

  private class NodeImpl(override var parent: NodeImpl? = null,
                         override var firstChild: NodeImpl? = null,
                         override var nextSibling: NodeImpl? = null): Node

}

This should work although it doesn’t stop “invalid” nodes from being passed in by users, but node ownership is always a problem in mutable trees (copying to a locally created not is always safer in that sense).

2 Likes

If you are separating your code into different modules you can even use an abstract class with an internal constructor instead of the interface. That way no one outside of the module can create “invalid” nodes.

1 Like

Oh, I see. I somehow missed that you can override val with var. Thanks!

As for stopping from passing “invalid” nodes — Node can be a sealed interface

1 Like

You’re right. I did forget about sealed as I have not used it myself so far :slight_smile:

Kotlin allows to do that but uses different default behavior.
Inner class in Kotlin by default behave like Java’s class with static modifier on inner class. So it’s completely separate class without reference to the parent.
If you want to get access to parent class like in java without static you should add modifier inner to Kotlin’s internal class.
It just forces you to be more explicit for such case and it’s harder to leak parent class accidentally like in java, when you just forgot to mark inner class static, it’s actually not the best practice in most cases

Gildor : He (and I) want to do the exact opposite of “inner”. Tree wants to modify the internals of Node, but also wants to hang out Node(s) to other clients that look immutable. Only Tree should be able to modify the guts of Node.

The solution given works, but… it’d be nice if the language supported it, instead of having to write boiler plate for this pattern every time.

3 Likes