Home > OS >  Kotlin polymorphism - receive argument cannot have type overriden, but return argument can?
Kotlin polymorphism - receive argument cannot have type overriden, but return argument can?

Time:05-28

Perhaps I'm tired or not thinking straight, or perhaps this is just something I've never tried before.

I have a base data type, let's call it Foo, and an interface, let's call this Problem which exposes some functions that work with this Foo data type.

One of the interface functions returns a Foo and one of the functions recieves a Foo.

Then, I inherit from Foo, to create a new data type, let's call it Bar, I then implement the Problem interface, and in the overrides, I override the type of Foo to be Bar.

For the function which returns the Foo, the usage of the Bar override works. However, for the function that receives the Foo, the usage of the Bar override does not work:

open class Foo(
    open val name: String
)

interface Problem {
    fun setFoo(foo: Foo)
    fun getFoo() : Foo
}

data class Bar(
    override val name: String
) : Foo(
    name = name
)

class ProblemImpl : Problem {
    override fun setFoo(foo: Bar) {
        TODO("Not yet implemented")
    }

    override fun getFoo(): Bar {
        TODO("Not yet implemented")
    }
}

setFoo overrides nothing

Now, it might seem obvious; just don't override the types like this! You can just pass Bar instances, and due to polymorphism, they will be received as Foo instances. But, for complicated reasons I won't go into here, I cannot do this (think declaring Dao interface in library, and overriding it in consuming app with Room annotations...)

I suppose I could have the interface be type parameterized... like:

interface Problem<T: Foo> {
   fun setFoo(foo: T)
   fun getFoo() : T
}

In fact, I think I've just answered my own question... I'll try with templating now, but I have a hunch it will fail at compile time as Room annotation processor might not be able to deal with template interfaces... in any case, can anyone tell me why the function that receives cannot be overriden with a subtype whilst the function that returns can?

CodePudding user response:

in any case, can anyone tell me why the function that receives cannot be overriden with a subtype whilst the function that returns can?

The limitation arises as a consequence of Liskov substitution principle: if Bar is a subtype of Foo, you should be able to use a Bar wherever you use a Foo.

Let's look at the two situations:

  1. The function getFoo() returns Foo. In a subtype, you return Bar. Since each Bar is a Foo (as Bar is a subtype of Foo), you can use your overridden version of getFoo() in each place you use the base version - it always returns a Foo (which just happens to be Bar, a specific type of Foo). Everything works just fine.
  2. The function setFoo() receives a Foo. In a subtype, you want it to accept Bar. Now, someone is using your subtype through a reference of the base type. They pass to setFoo() a Foo which is not a Bar. But your setFoo() in this case only accepts Bar. This means you can't use your subclass in place of the base class, which violates Liskov substitution principle and causes the whole idea of subclassing to collapse. Therefore, the choice is for the language to not allow a subclass to accept Bar in place of Foo.

You can also read on covariance and contravariance for more exploration of the topic when subtypes can be used in place of their supertypes.

  • Related