Reputation: 3104
Having a class:
abstract class Bird<T: Food>
and a subclass, where Fish
extends Food
:
class Falcon: Bird<Fish>()
How do I create a method that returns a Bird
:
fun getBird(type: Int): Bird<Food> {
if (type == SOME_CONSTANT) {
return Falcon()
} else {
// Another bird
}
}
Getting:
Type mismatch: Required Bird<Food>, found Falcon
Upvotes: 0
Views: 120
Reputation: 89668
You basically have two options to solve this:
Using declaration site variance. This is preferred when possible.
What you need for this to work is Bird<Fish>
(which your Falcon
is) to be a subtype of Bird<Food>
(which you're returning). Since Fish
is also a subtype of Food
, what you need here is covariance, which you can specify for the type parameter with the out
keyword:
abstract class Bird<out T: Food>
Now, you've added below in your comment that you have a mutable property of type T?
in your base class, something like:
abstract class Bird<out T: Food> {
var food: T? = null
}
This is an issue for declaration site variance. If you want covariance to work, the type parameter can only be present in your class in out
positions, meaning you can have functions that return T
, but you can't have ones that accept T
(in this case, that would be the setter of the property). Think about it: if you had a reference to a Falcon
as a Bird<Food>
because you got it from the getBird
function, you could suddenly feed it any Food
, not just Fish
. This would break type safety. Which gets us to the other solution...
Using use site variance.
This is less ideal, but sometimes necessary (like apparently in your case, because of the mutable property of type T?
in your base class. Credit for this solution goes to @tynn's very good answer, but let me expand on it.
You can mark covariance on just the return type of the function instead of the entire abstract class by making the function return a Bird<out Food>
.
fun getBird(type: Int): Bird<out Food>
Doing this will let you have your T
parameter in both in
and out
positions in your class, but prevent you from using any functions that take T
(have it in the in
position) on the Bird
instances that you get from the getBird
function.
So you could do this after the change:
println(getBird(1).food)
But not this for any value of newFood
that's not null
(since a variable of type Nothing?
is required here, which only null
is):
getBird(1).food = newFood
This restriction of not being able to set the food
property of the Bird
returned by the function is what "restores" type safety in this case.
Upvotes: 1
Reputation: 39873
You have to define the out
variance on the return type of getBird()
fun getBird(type: Int): Bird<out Food>
Like this you can return birds with any food source without changing the Bird
class.
Upvotes: 1