87

I have two classes Entity and Account as

abstract class Entity(
    var id: String? = null,
    var created: Date? = Date()) {

    constructor(entity: Entity?) : this() {
        fromEntity(entity)
    }

    fun fromEntity(entity: Entity?): Entity {
        id = entity?.id
        created = entity?.created
        return this;
    }
}

and

data class Account( 
    var name: String? = null,
    var accountFlags: Int? = null
) : Entity() {

    constructor(entity: Entity) : this() {
        super(entity)
    }
}

Which gives me the error

Super is not an expression, it can be only used in the left-hand side of a dot '.'

Why cannot I do that?

The following will pass the compilation error, but I am not sure if it is correct.

 constructor(entity: Entity) : this() {
    super.fromEntity(entity)
}
Amr Eladawy
  • 4,193
  • 7
  • 34
  • 52
  • 3
    You can find the rules for how you can call super ctors here: https://kotlinlang.org/docs/reference/classes.html#inheritance . In short, if the superclass has a primary constructor, you must call that one from all constructors of the base class. – zsmb13 Jun 11 '17 at 07:22
  • `constructor(entity: Entity) : super(entity)` – Miha_x64 Jun 11 '17 at 08:33
  • @zsmb13 can you please share some code? – Amr Eladawy Jun 11 '17 at 10:31
  • @Miha_x64 I tried that already, and it gives `Primary constructor call is expected`. – Amr Eladawy Jun 11 '17 at 10:32

5 Answers5

123

You have a couple of problems in your code.

First, this is the correct syntax to call a super constructor from a secondary constructor:

constructor(entity: Entity) : super(entity)

Second, you can't call a super constructor from a secondary constructor if your class has a primary constructor (which your class does).

Solution 1

abstract class Entity(
        var id: String,
        var created: Date
)

class Account(
        var name: String,
        var accountFlags: Int,
        id: String,
        created: Date
) : Entity(id, created) {
    constructor(account: Account) : this(account.name, account.accountFlags, account.id, account.created)
}

Here, the copy constructor is in the child class which just delegates to the primary constructor.

Solution 2

abstract class Entity(
        var id: String,
        var created: Date
) {
    constructor(entity: Entity) : this(entity.id, entity.created)
}

class Account : Entity {
    var name: String
    var accountFlags: Int

    constructor(name: String, accountFlags: Int, id: String, created: Date) : super(id, created) {
        this.name = name
        this.accountFlags = accountFlags
    }

    constructor(account: Account) : super(account) {
        this.name = account.name
        this.accountFlags = account.accountFlags
    }
}

Here I'm only using secondary constructors in the child class, which lets me delegate them to individual super constructors. Notice how the code is pretty long.

Solution 3 (most idiomatic)

abstract class Entity {
    abstract var id: String
    abstract var created: Date
}

data class Account(
        var name: String,
        var accountFlags: Int,
        override var id: String,
        override var created: Date
) : Entity()

Here I omitted the copy constructors and made the properties abstract, so the child class has all the properties. I also made the child class a data class. If you need to clone the class, you can simply call account.copy().

Kirill Rakhman
  • 42,195
  • 18
  • 124
  • 148
  • 2
    Thanks. I already tried the syntax `constructor(entity: Entity) : super(entity)` but it gives the same error `super is not an expression...` I did not try your suggested code, but I have one concern. What is benefit of having a parent if I have to define the same fields again in every child? – Amr Eladawy Jun 12 '17 at 07:34
  • 1
    Like I said in my answer, "you can't call a super constructor from a secondary constructor if your class has a primary constructor". Regarding your concern, the child class doesn't have the same **fields**. It has constructor parameters which it passes to the super class which defines the properties. – Kirill Rakhman Jun 12 '17 at 07:38
  • 1
    Can I call the parent constructor if I removed the primary constructor from the child? Can you update your answer with sample code for that? – Amr Eladawy Jun 12 '17 at 07:39
  • @AmrElAdawy I've updated my answer with two more solutions. – Kirill Rakhman Jun 12 '17 at 07:47
  • thanks for the update. I will vote up for the efforts. I still have many concerns on solutions. They require losing the `data` nature and still need to add the parent fields in the children which I can avoid by removing the parent altogether. I still see my question has the less coding. – Amr Eladawy Jun 12 '17 at 08:56
  • @AmrElAdawy I forgot to make the child class a `data class` in solution 3. Please see the updated answer. – Kirill Rakhman Jun 12 '17 at 09:15
  • The only downside to the idiomatic approach is that you expose the fields `id` and `created` - allowing anyone to pass in a random id and a bogus creation date. – Sync Oct 08 '19 at 21:12
16

You can also move your primary constructor down into the class like this:

data class Account: Entity {
    constructor(): super()
    constructor(var name: String? = null, var accountFlags: Int? = null): super()
    constructor(entity: Entity) : super(entity)
}

Advantage of this is, compiler will not require your secondary constructor to call primary constructor.

Jeff
  • 293
  • 4
  • 13
2

Another option is to create companion object and provide factory method e.g.

class Account constructor(
        var name: String? = null,
        var accountFlags: Int? = null,
        id: String?,
        created: Date?
) : Entity(id, created) {

    companion object {
        fun fromEntity(entity: Entity): Account {
            return Account(null, null, entity.id, entity.created)
        }
    }
}
ZZ 5
  • 1,744
  • 26
  • 41
0

Use this super<Entity>.fromEntity(entity) to call super class methods.

As Documentation says:

In Kotlin, implementation inheritance is regulated by the following rule: if a class inherits many implementations of the same member from its immediate superclasses, it must override this member and provide its own implementation (perhaps, using one of the inherited ones). To denote the supertype from which the inherited implementation is taken, we use super qualified by the supertype name in angle brackets, e.g. super.

constructor(entity: Entity) : this() {
    super<Entity>.fromEntity(entity)
}

To know more read Overriding Rules

Sachin Chandil
  • 17,133
  • 8
  • 47
  • 65
  • Thanks for the reply. I am aware of denoting the supertype. But this is actually a workaround. I want to use the super constructor. – Amr Eladawy Jun 11 '17 at 10:29
0

That's what I was looking for, no modifier or val/var on argument to have it used for parent(super) call

open class Dad(protected val name: String){
}

open class Son(protected val nickname: String, name: String): Dad(name){
...
}
Pipo
  • 4,653
  • 38
  • 47