我正在查看 dart 文档,在那里我遇到了这段代码和这个术语
covariant
。我浏览了一些文档,但我不明白它的用途是什么。详细解释的答案总是值得赞赏。
class Animal {
void chase(Animal x) { ... }
}
class Mouse extends Animal { ... }
class Cat extends Animal {
@override
void chase(covariant Mouse x) { ... }
}
在 Dart 中,如果重写超类方法,则重写方法的参数必须具有 与原来的类型相同。
由于示例中的
Animal.chase
接受 Animal
参数,因此您必须在覆盖中执行相同的操作:
class Cat extends Animal {
@override
void chase(Animal x) { ... }
}
为什么?想象一下,如果没有这样的限制。
Cat
可以定义 void chase(Mouse x)
而 Dog
可以
定义void chase(Cat x)
。然后想象你有一个 List<Animal> animals
并且你拨打 chase(cat)
其中之一。如果动物是狗,它会起作用,但如果动物是猫,猫就不是老鼠!那只猫
类无法处理被要求追逐另一只猫的情况。
所以你被迫使用
void chase(Animal x)
。我们可以模拟一个 void chase(Mouse x)
类型签名
通过添加运行时类型检查:
void chase(Animal x) {
if (x is Mouse) {
/* do chase */
} else {
/* throw error */
}
}
事实证明这是一个相当常见的操作,如果能在编译时检查就更好了 在可能的情况。因此 Dart 添加了一个
covariant
运算符。将函数签名更改为 chase(covariant Mouse x)
(其中 Mouse 是 Animal 的子类)做三件事:
x is Mouse
检查,因为它已为您完成。Cat.chase(x)
,其中 x 不是 Mouse 或其子类(如果在编译时已知),则会创建编译时错误。另一个例子是对象上的
operator ==(Object x)
方法。假设你有课Point
:
您可以这样实现
operator==
:
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;
}
}
}
但是即使您比较
Point(1,2) == "string"
或数字或其他对象,此代码也会编译。将点与非点的事物进行比较是没有意义的。
你可以使用
covariant
告诉 Dart other
应该是一个 Point,否则会出错。这也可以让您删除 other is Point
部分:
bool operator==(covariant Point other) =>
x == other.x && y == other.y;
为什么叫“协变”?
协变是一个奇特的类型理论术语,但它基本上意味着“这个类或其子类”。换句话说,它意味着类型 在类型层次结构中等于或较低。
您明确告诉 Dart 将此参数的类型检查加强到原始参数的子类。 第一个例子:将Animal收紧为Mouse;第二个:将对象收紧到点。
有用的相关术语是逆变,这意味着类型层次结构中等于或更高的类型,以及不变, 这正是这种类型。
有关更多信息,这个 Stack Overflow 问题是一个很好的资源。
通过使用协变关键字,您可以禁用类型检查并负责确保您在实践中不违反合同。
正如您在示例中看到的,如果您要重写一个方法,它的参数也应该是相同的。但如果您使用协变,它将允许您使用鼠标而不是动物。
Covariant 是一个关键字,允许您使用作为原始类型的子类的新参数类型来重写方法。当您希望为不同类型的参数提供更精确的行为而不破坏与超类函数的兼容性时,这非常方便。
class A {}
class B extends A {
}
class C extends B {
}
class D extends A {}
这些是一些简单的类,我将用它们来演示不同类型的参数。
// Create a class with a method
class P {
void method(B b) {}
}
// You can create another class and override the
// method without changing the type of the parameter.
class Q extends P {
@override
void method(B b) {}
}
// Without any problem, you can override
// the method to take a superclass as an argument.
class R extends P {
@override
void method(A a) {}
}
// But if you override to take a subclass
// it gives an error.
class S extends P {
@override
void method(C c) {} // Error
}
// You can avoid this error by
// adding the `covariant` keyword.
class T extends P {
@override
void method(covariant C c) {}
}
// You can even add `covariant` with
// a superclass of the original type.
class U extends P {
@override
void method(covariant A a) {}
}
// But you can't use this to override
// methods to take arguments with unrelated types.
// (Not in the hierarchy of the original type)
class V extends P {
@override
void method(covariant D d) {} // Error
}