The following is a story about "animal eats food", and there is a cat eating a fish.
class Food
abstract class Animal {
type F
def eat(food: F)
}
class Fish extends Food
class Cat extends Animal {
type F = Fish
def eat(fish: F) {
println("eat " + fish.getClass.getSimpleName)
}
}
(new Cat).eat(new Fish) //eat Fish
val animal: Animal = new Cat
animal.eat(new Fish) //error: type mismatch
Now that I have used abstract-type member (or type-parameter) , I lose the runtime polymorphism
(at last line). (that using base type Animal to typing arbitrary subtypes and run with no problem.)
Otherwise I can remove the type parameter from Animal and type checking in Cat:
abstract class Animal {
def eat(food: Food)
}
class Cat extends Animal {
def eat(food: Food) {
food match {
case fish: Fish => println("eat" + fish)
case _ => throw new IllegalArgumentException("I only eat fish")
}
}
}
But I want the better typing for them .
So can I preserve runtime polymorphism while using type-parameter / generic ?
Now that I have used abstract-type member (or type-parameter) , I lose the runtime polymorphism (at last line). (that using base type Animal to typing arbitrary subtypes and run with no problem.)
No, you can replace Animal
by its subtypes (in context like this, not always!), but not vice versa. If you could, you would be able to use Object
as well, since it's also a base type of Cat
:
val object: Object = new Cat
object.eat(new Fish)
Hopefully you see why this shouldn't compile.
Or you could put it another way: should
val animal: Animal = makeAnAnimal()
animal.eat(new Fish)
compile? If you think the answer is "yes", consider that makeAnAnimal()
could return a Cow
.