今天,我阅读了一些有关 Java 中的协变、逆变(和不变)的文章。我阅读了英文和德文维基百科文章,以及 IBM 的其他一些博客文章和文章。
但我对这些到底是什么还是有点困惑?有人说它是关于类型和子类型之间的关系,有人说它是关于类型转换,有人说它用于决定一个方法是否被覆盖或重载。
所以我正在寻找一个简单的英语解释,向初学者展示协变和逆变(和不变)是什么。加点一个简单的例子。
原文由 anon 发布,翻译遵循 CC BY-SA 4.0 许可协议
今天,我阅读了一些有关 Java 中的协变、逆变(和不变)的文章。我阅读了英文和德文维基百科文章,以及 IBM 的其他一些博客文章和文章。
但我对这些到底是什么还是有点困惑?有人说它是关于类型和子类型之间的关系,有人说它是关于类型转换,有人说它用于决定一个方法是否被覆盖或重载。
所以我正在寻找一个简单的英语解释,向初学者展示协变和逆变(和不变)是什么。加点一个简单的例子。
原文由 anon 发布,翻译遵循 CC BY-SA 4.0 许可协议
差异是关于具有不同泛型参数的类之间的关系。他们的关系是我们可以施放他们的原因。
Co 和 Contra 方差是非常合乎逻辑的事情。语言类型系统迫使我们支持现实生活中的逻辑。通过例子很容易理解。
例如,您想买一朵花,而您所在的城市有两家花店:玫瑰店和雏菊店。
如果你问某人“花店在哪里?”有人告诉你玫瑰店在哪里,可以吗?是的,因为玫瑰是一种花,如果你想买一朵花,你可以买一朵玫瑰。如果有人用雏菊店的地址回复你,这同样适用。 This is example of covariance : you are allowed to cast A<C>
to A<B>
, where C
is a subclass of B
, if A
生成通用值(作为函数的结果返回)。协方差与生产者有关。
类型:
class Flower { }
class Rose extends Flower { }
class Daisy extends Flower { }
interface FlowerShop<T extends Flower> {
T getFlower();
}
class RoseShop implements FlowerShop<Rose> {
@Override
public Rose getFlower() {
return new Rose();
}
}
class DaisyShop implements FlowerShop<Daisy> {
@Override
public Daisy getFlower() {
return new Daisy();
}
}
问题是“花店在哪里?”,答案是“那里有玫瑰店”:
static FlowerShop<? extends Flower> tellMeShopAddress() {
return new RoseShop();
}
例如你想送花给你的女朋友。如果你的女朋友喜欢什么花,你能把她看成是爱玫瑰的人,还是爱雏菊的人?是的,因为如果她喜欢任何一朵花,她会同时喜欢玫瑰和雏菊。 This is an example of the contravariance : you’re allowed to cast A<B>
to A<C>
, where C
is subclass of B
, if A
消耗通用值。逆变是关于消费者的。
类型:
interface PrettyGirl<TFavouriteFlower extends Flower> {
void takeGift(TFavouriteFlower flower);
}
class AnyFlowerLover implements PrettyGirl<Flower> {
@Override
public void takeGift(Flower flower) {
System.out.println("I like all flowers!");
}
}
你把喜欢任何花的女朋友当成喜欢玫瑰的人,送她一朵玫瑰:
PrettyGirl<? super Rose> girlfriend = new AnyFlowerLover();
girlfriend.takeGift(new Rose());
您可以在 Source 找到更多信息。
原文由 VadzimV 发布,翻译遵循 CC BY-SA 4.0 许可协议
4 回答1.5k 阅读✓ 已解决
4 回答1.3k 阅读✓ 已解决
1 回答2.6k 阅读✓ 已解决
2 回答749 阅读✓ 已解决
2 回答1.8k 阅读
2 回答1.7k 阅读
2 回答1.3k 阅读
上述所有的。
从本质上讲,这些术语描述了类型转换如何影响子类型关系。 That is, if
A
andB
are types,f
is a type transformation, and ≤ the subtype relation (ieA ≤ B
means thatA
是B
的子类型,我们有f
是协变的如果A ≤ B
意味着f(A) ≤ f(B)
f
是逆变的,如果A ≤ B
意味着f(B) ≤ f(A)
f
如果以上都不成立则不变让我们考虑一个例子。让
f(A) = List<A>
其中List
由声明f
是协变的、逆变的还是不变的? Covariant would mean that aList<String>
is a subtype ofList<Object>
, contravariant that aList<Object>
is a subtype ofList<String>
and invariant that neither is另一个的子类型,即List<String>
和List<Object>
是不可转换的类型。在 Java 中,后者是正确的,我们说(有点非正式地) 泛型 是不变的。另一个例子。让
f(A) = A[]
。f
是协变的、逆变的还是不变的?也就是说,String[] 是 Object[] 的子类型,Object[] 是 String[] 的子类型,还是两者都不是另一个的子类型? (答案:在 Java 中,数组是协变的)这还是比较抽象的。为了使其更具体,让我们看看 Java 中的哪些操作是根据子类型关系定义的。最简单的例子就是赋值。该声明
仅当
typeof(y) ≤ typeof(x)
时才会编译。也就是说,我们刚刚了解到这些陈述不会在 Java 中编译,但是
将要。
子类型关系很重要的另一个例子是方法调用表达式:
通俗地说,通过将
a
的值赋值给方法的第一个参数,然后执行方法的主体,然后将方法返回值赋值给result
--- 来评估该语句。就像上一个例子中的普通赋值一样,“右手边”必须是“左手边”的子类型,即这个语句只有在typeof(a) ≤ typeof(parameter(method))
和returntype(method) ≤ typeof(result)
时才有效.也就是说,如果方法声明为:以下表达式都不会编译:
但
将要。
子类型很重要的另一个例子是重写。考虑:
在哪里
非正式地,运行时会将其重写为:
对于要编译的标记行,覆盖方法的方法参数必须是被覆盖方法的方法参数的超类型,返回类型是被覆盖方法的子类型。正式地说,
f(A) = parametertype(method asdeclaredin(A))
必须至少是逆变的,如果f(A) = returntype(method asdeclaredin(A))
必须至少是协变的。注意上面的“至少”。这些是任何合理的静态类型安全面向对象编程语言将强制执行的最低要求,但编程语言可能会选择更严格。在 Java 1.4 的情况下,重写方法时参数类型和方法返回类型必须相同(类型擦除除外),即
parametertype(method asdeclaredin(A)) = parametertype(method asdeclaredin(B))
重写时。从 Java 1.5 开始,覆盖时允许协变返回类型,即以下内容将在 Java 1.5 中编译,但在 Java 1.4 中不编译:我希望我涵盖了所有内容——或者更确切地说,只触及了表面。我仍然希望它有助于理解抽象但重要的类型变化概念。