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:
- 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. - 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 tosetFoo()
a Foo which is not a Bar. But yoursetFoo()
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.