In Dart, if you override a superclass method, you must keep the type the same. Since Animal.chase in your example accepts an argument of Animal, you must do the same in your override:
class Cat extends Animal {
@override
void chase(Animal x) { ... }
}
Why? Because changing the type would make the subclass incompatible with the superclass. For example say you made a list containing both Animals and Cats (type List<Animal>. Calling list[i].chase(cat) would be valid for Animals but invalid for Cats!
If you want the Cat to only chase animals to type Mouse, you would then add a runtime type check:
void chase(Animal x) {
if (x is Mouse) {
/* do chase */
} else {
/* throw error */
}
}
It turns out this is a fairly common operation, and it would be nicer if it could be checked at compile time. So Dart added a covariant operator. Changing the function signature to chase(covariant Mouse x) (where Mouse is a subclass of Animal) does three things:
- Allows you to skip the
x is Mouse check, as it is done for you
- Creates a compile time error if any Dart code calls
Cat.chase(x) where x is not a Mouse or its subclass. (if known at compile time)
- Creates a runtime error in other cases.
Another example is the operator ==(Object x) method on objects. Say you have a class Point:
You could implement operator== this way:
class Point {
final int x, y;
Point(this.x, this.y);
bool operator==(Object other) {
if (other is Point) {
return x == other.x && y == other.y;
} else {
return false;
}
}
}
But this code compiles even if you compare Point(1,2) == "string" or a number or some other object. It makes no sense to compare a Point with things that aren't Points.
You can use covariant to tell Dart that other should be a Point, otherwise it's an error. This lets you drop the x is Point part, too:
bool operator==(covariant Point other) =>
x == other.x && y == other.y;
Why is it called 'covariant'?
Covariant is a fancy type theory term, but it basically means 'this class or its subclasses'. You are explicitly telling Dart to tighten the type checking of this argument to a subclass of the original. Ffor the first example: tightening Animal to Mouse; for the second: tightening Object to Point.