Question regarding sealed classes

I was reading the documentation of sealed classes https://kotlinlang.org/docs/reference/sealed-classes.html

sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()

Like the example code, why do we put the parentheses for the case of sealed classes,
e.g. like : Expr(), unlike normal classes where we would write : Expr ?
Doesn’t Expr() mean instantiating the class? If this is the case, it would contradict to the sentence from the documentation

A sealed class is abstract by itself, it cannot be instantiated directly and can have abstract members.

I also don’t understand why here

Sum(val e1: Expr, val e2: Expr) : Expr()

for properties e1 and e2 we write : Expr while for class Sum we write : Expr()

How should I understand this, and what’s happening under the hood?

EDIT:
I discovered that the use of parentheses is not only limited to the case of sealed classes.
https://kotlinlang.org/docs/reference/classes.html

How should I understand the parentheses?

Whenever a class inherits from another it also needs to call the constructor of it’s parent. This is also true for sealed classes.

This is not true if the derived class has a primary constructor. In that case you need to specify what constructor of the base class you want to call so you have to add the parenthesis.

https://kotlinlang.org/docs/referencehttps://kotlinlang.org/docs/reference/classes.html#inheritance

To declare an explicit supertype, place the type after a colon in the class header:

open class Base(p: Int)
class Derived(p: Int) : Base(p)

If the derived class has a primary constructor, the base class can (and must) be initialized right there, using the parameters of the primary constructor.

If the derived class has no primary constructor, then each secondary constructor has to initialize the base type using the super keyword, or to delegate to another constructor which does that. Note that in this case different secondary constructors can call different constructors of the base type:

class MyView : View {
   constructor(ctx: Context) : super(ctx)
   constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
}
1 Like

Thank you for the explanation.

For these kind of cases,

class Invoice { /*...*/ }
class Empty
sealed class Expr

Does it mean that there is NO primary constructor, or is the primary constructor the one without any parameters, like Empty(), Invoice(), Expr()?

The primary constructor is the constructor that comes before the braces. So

class Foo /* primary constructor */ : BaseClass() {
}

The only way not to have a primary constructor is to add a secondary constructor

class Foo : BaseClass {
    constructor(): super() {}
}

You can have as many secondary constructors as you want and you can also combine secondary constructors with a primary. In this case each secondary constructor has to either call another secondary constructor or the primary constructor

class Foo (val i: Int) : BaseClass() {
     constructor(i: Int, someBoolean: Boolean): this(i) { // calls primary constructor
         if(someBoolean) TODO()
     }
    constructor() : this(5, true) // calls a secondary constructor
    constructor(s: String) : this(s.toInt()) // also call the primary constructor
}

The only way not to have a primary constructor is to add a secondary constructor

class Foo : BaseClass {
    constructor(): super() {}
}

Then could a class which doesn’t inherit from any other class also not have a primary constructor?

You mean like

class Foo {
    constructor()
}

Sure.

Thank you, that cleared things up!