0

I have interface A which has interface B as a field. When I do an implementation of A, let's say concreteA, and instead of providing interface B, I provide an implementation of B, the compiler throws me an error. I don't understand why, because since there is inheritance in Kotlin, I think this should work. I tried this with data classes by the way.


interface B {
   ....
}

data class concreteB : B {
   .....
}

interface A {
   var someField: B
}

data class concreteA : A {
   //The error happens here, compiler says it's not type of overridden
   override var someField: concreteB
}
Tom
  • 1
  • 1
  • 1
    I talked with Kotlin guys on Slack, the explanation is LSP (Liskov Substitution Principle) reference: https://stackoverflow.com/questions/56860/what-is-an-example-of-the-liskov-substitution-principle. Seems like in the Kotlin it is not possible to achieve it, however I could be wrong. – J.D.1731 Aug 20 '19 at 06:03

2 Answers2

1

The fact that you inherited something from your dad doesn't mean that you're your dad.

The usage of data classes here is a bit confusing, since your code won't compile with them.

That's the correct way for your code to work:

interface B

class ConcreteB : B

interface A {
    var someField: B
}

class ConcreteA : A {
    override var someField: B = // That's the interface
        ConcreteB()             // That's the implementation
}
Alexey Soshin
  • 16,718
  • 2
  • 31
  • 40
  • The thing is that those data classes are spring entities I would like to reference other entities in, since I would run into problems saving those entities otherwise. Is your example the only way to implement this? I thought for example of declaring field B in the entity in a generic way. – Tom Aug 19 '19 at 11:48
  • But that's not what you were asking in the original question at all :) – Alexey Soshin Aug 19 '19 at 11:57
1

The definition of A says that every implementation must have a field storing an instance of something implementing B.  This field is var, meaning that other code can change it.

In your ConcreteA, you're overriding that to restrict the field to hold only your ConcreteB.

Now, that's fine for code calling the getter, as it will always get something of type B.

But it's a problem for code calling the setter.  What if they try to set some other implementation of B?  The interface says they can, but your concrete implementation can't.  That's why the compiler won't allow your implementation.  (That's the Liskov Substitution Principle in action, as the Kotlin guys said.)

There are several approaches you could take to make it compile:

  • You could use a val instead of a var in A, and not allow anyone else to change the value.  (Simple, but probably not what you want.)

  • You could parameterise A with the type of value, giving B as an upper bound.  That would let implementations (and subinterfaces) restrict the type to ConcreteB if they wanted to.  (That's a bit more complex, but is very flexible and may be best suited to your needs.)

  • You could keep the type of the field as B, and just initialise it to your ConcreteB (as per Alexey's answer).  (However, your ConcreteB would have to allow for the possibility of other B implementations being set later on.)

gidds
  • 16,558
  • 2
  • 19
  • 26