既然Any 可以容纳任何类型,而且String 是低级类型,为什么我不能将(String) -> String 函数转换为(Any) -> Any 函数?
因为两者没有任何关系。
是的,String <:>Any 是真的。这意味着您可以将String 转换为Any。但是您并没有尝试将String 转换为Any。您正在尝试将(String) -> String 转换为(Any) -> Any,这是String 和Any 的两种完全不同的类型。 (String) -> String 与String 不同,(Any) -> Any 与Any 不同,因此绝对没有理由为什么String 和Any 的关系也应该自动为@987654341 成立@ 和 (Any) -> Any ... 正如您所发现的,事实上,这种关系确实不成立。
简短的回答是:函数的参数类型是逆变的,而返回类型是协变的。因此,(String) -> String 将是(String) -> Any 的子类型和(Any) -> String 的超类型,它既不是(Any) -> Any 的子类型也不是超类型。
在 1970 年代初期,一位名叫 Barbara Liskov 的计算机科学家发明了一种根据行为替换来思考子类型化的新方法,我们现在称之为 Liskov 替换原则 (LSP)时间>。我们可以使用 LSP 准确地解释为什么函数的参数类型是逆变的,而返回类型是协变的。 (注意:这在 Liskov 之前就已经众所周知,但是 LSP 为我们提供了一种很好的方式来解释为什么会这样。)
Barbara Liskov's Substitution Principle 告诉我们 S 类型是 T 类型的子类型 IFF T 的任何实例都可以替换为 S 的实例而不改变 observable程序的理想属性。
让我们看一个简单的泛型类型,一个函数。一个函数有两个类型参数,一个用于输入,一个用于输出。 (我们在这里保持简单。)(A) -> B 是一个函数,它接受A 类型的参数并返回B 类型的结果。
现在我们播放几个场景。我有一些操作 O 想要使用从 Fruits 到 Mammals 的函数(是的,我知道,令人兴奋的原始示例!)LSP 说我也应该能够传入该函数的子类型,一切都应该仍然有效。假设,函数在A 中是协变的。然后我应该也可以将一个函数从Apples 传递给Mammals。但是当 O 将 Orange 传递给函数时会发生什么?这应该被允许! O 能够将Orange 传递给(Fruit) -> Mammal,因为Orange 是Fruit 的子类型。但是,来自Apples 的函数不知道如何处理Oranges,所以它崩溃了。 LSP 说它应该可以工作,这意味着我们可以得出的唯一结论是我们的假设是错误的:(Apple) -> Mammal 不是(Fruit)-> Mammal 的子类型,换句话说,函数在A 中不是协变的。
如果它是逆变的呢?如果我们将(Food) -> Mammal 传递给O 会怎样?好吧,O 再次尝试传递Orange 并且它有效:Orange 是Food,所以(Food) -> Mammal) 知道如何处理Oranges。我们现在可以得出结论,函数在其输入中是逆变的,也就是说,您可以传递一个采用更通用类型作为其输入的函数来替代采用更受限制类型的函数,并且一切都会正常工作很好。
现在让我们看看函数的返回类型。如果函数在 B 中是逆变的,就像在 A 中一样,会发生什么?我们将(Fruit) -> Animal 传递给O。根据 LSP,如果我们是对的并且函数的返回类型是逆变的,那么就不会发生任何不好的事情。不幸的是,O 对函数的结果调用了getMilk 方法,但函数只是返回了一个Chicken。哎呀。因此,函数的返回类型不能是逆变的。
OTOH,如果我们通过 (Fruit) -> Cow 会发生什么?一切仍然有效! O 在返回的奶牛上调用getMilk,它确实产奶了。因此,看起来函数的输出是协变的。
这是适用于方差的一般规则:
- 使
A 中的C<A> 协变 是安全的(就LSP 而言)IFF 仅使用A em> 作为输出。
- 在
A 中制作C<A> 逆变 是安全的(在LSP 的意义上)IFF A 仅使用 em> 作为输入。
- 如果
A 可以用作输入或输出,那么C<A> 必须在A 中保持不变,否则结果不安全。