用简单的英语解释协变、不变和逆变?

发布于 2024-12-21 02:44:01 字数 203 浏览 5 评论 0原文

今天,我读了一些关于Java中的协变、逆变(和不变性)的文章。我阅读了英语和德语维基百科文章,以及 IBM 的其他一些博客文章和文章。

但我对这些术语的含义仍然有点困惑。有人说它是关于类型和子类型之间的关系,有人说它是关于类型转换,有人说它用于决定一个方法是否被覆盖或重载。

我正在寻找一种简单的英语解释,向初学者展示协变和逆变(和不变性)是什么。简单的例子加分。

Today, I read some articles about covariance, contravariance (and invariance) in Java. I read the English and German Wikipedia articles, and some other blog posts and articles from IBM.

But I'm still a little bit confused about what these terms are about. Some say it's about the relationship between types and subtypes, some say it's about type conversion and some say it's used to decide whether a method is overridden or overloaded.

I'm looking for an easy explanation in plain English that shows a beginner what covariance and contravariance (and invariance) are. Extra points for an easy example.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

煮茶煮酒煮时光 2024-12-28 02:44:01

有人说它是关于类型和子类型之间的关系,有人说它是关于类型转换,有人说它是用来决定一个方法是否被覆盖或重载。

上述所有的。

从本质上讲,这些术语描述了类型转换如何影响子类型关系。也就是说,如果 AB 是类型,则 f 是类型转换,并且 ≤ 子类型关系(即 A ≤ B 表示 AB 的子类型),

  • 如果 A ≤ B,则 f 是协变的> 意味着 f(A) ≤ f(B)
  • 如果 A ≤ B 意味着 f(B) ≤ f(A),则
  • f 是逆变的 如果以上都不成立,则 f 是不变的

让我们考虑一个例子。设 f(A) = List,其中 List 声明为

class List<T> { ... } 

f 是协变、逆变还是不变?协变意味着 ListList的子类型,逆变意味着 ListList的子类型List 并且不变,两者都不是另一个的子类型,即 ListList是不可转换的类型。在 Java 中,后者是正确的,我们(有点非正式地)说泛型是不变的。

另一个例子。令f(A) = A[]f 是协变、逆变还是不变?也就是说,String[] 是 Object[] 的子类型,Object[] 是 String[] 的子类型,还是两者都不是另一个的子类型? (答案:在 Java 中,数组是协变的)

这仍然相当抽象。为了更具体,让我们看看Java中哪些操作是根据子类型关系定义的。最简单的例子就是赋值。 该语句

x = y;

仅当 typeof(y) ≤ typeof(x) 时, 才会编译。也就是说,我们刚刚了解到这些语句

ArrayList<String> strings = new ArrayList<Object>();
ArrayList<Object> objects = new ArrayList<String>();

不会在 Java 中编译,但

Object[] objects = new String[1];

可以。

子类型关系重要的另一个例子是方法调用表达式:

result = method(a);

通俗地说,该语句的计算方法是将 a 的值分配给方法的第一个参数,然后执行方法的主体,然后分配这些方法将值返回到结果。与上一个示例中的简单赋值类似,“右侧”必须是“左侧”的子类型,即此语句仅在 typeof(a) ≤ typeof(parameter(method)) 时才有效returntype(method) ≤ typeof(result)。也就是说,如果方法声明为:

Number[] method(ArrayList<Number> list) { ... }

以下表达式都不会编译:

Integer[] result = method(new ArrayList<Integer>());
Number[] result = method(new ArrayList<Integer>());
Object[] result = method(new ArrayList<Object>());

Number[] result = method(new ArrayList<Number>());
Object[] result = method(new ArrayList<Number>());

会编译。

子类型很重要的另一个例子是压倒性的。考虑:

Super sup = new Sub();
Number n = sup.method(1);

where

class Super {
    Number method(Number n) { ... }
}

class Sub extends Super {
    @Override 
    Number method(Number n);
}

非正式地,运行时将其重写为:

class Super {
    Number method(Number n) {
        if (this instanceof Sub) {
            return ((Sub) this).method(n);  // *
        } else {
            ... 
        }
    }
}

对于要编译的标记行,重写方法的方法参数必须是被重写方法的方法参数的超类型,并且返回类型是被重写方法的方法参数的子类型。正式来说,f(A) =parametertype(method asdeclaredin(A)) 至少必须是逆变的,并且 if 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 中编译:

class Collection {
    Iterator iterator() { ... }
}

class List extends Collection {
    @Override 
    ListIterator iterator() { ... }
}

我希望我涵盖了所有内容 - 或者更确切地说,只触及了表面。我仍然希望它有助于理解类型方差这个抽象但重要的概念。

Some say it is about relationship between types and subtypes, other say it is about type conversion and others say it is used to decide whether a method is overwritten or overloaded.

All of the above.

At heart, these terms describe how the subtype relation is affected by type transformations. That is, if A and B are types, f is a type transformation, and ≤ the subtype relation (i.e. A ≤ B means that A is a subtype of B), we have

  • f is covariant if A ≤ B implies that f(A) ≤ f(B)
  • f is contravariant if A ≤ B implies that f(B) ≤ f(A)
  • f is invariant if neither of the above holds

Let's consider an example. Let f(A) = List<A> where List is declared by

class List<T> { ... } 

Is f covariant, contravariant, or invariant? Covariant would mean that a List<String> is a subtype of List<Object>, contravariant that a List<Object> is a subtype of List<String> and invariant that neither is a subtype of the other, i.e. List<String> and List<Object> are inconvertible types. In Java, the latter is true, we say (somewhat informally) that generics are invariant.

Another example. Let f(A) = A[]. Is f covariant, contravariant, or invariant? That is, is String[] a subtype of Object[], Object[] a subtype of String[], or is neither a subtype of the other? (Answer: In Java, arrays are covariant)

This was still rather abstract. To make it more concrete, let's look at which operations in Java are defined in terms of the subtype relation. The simplest example is assignment. The statement

x = y;

will compile only if typeof(y) ≤ typeof(x). That is, we have just learned that the statements

ArrayList<String> strings = new ArrayList<Object>();
ArrayList<Object> objects = new ArrayList<String>();

will not compile in Java, but

Object[] objects = new String[1];

will.

Another example where the subtype relation matters is a method invocation expression:

result = method(a);

Informally speaking, this statement is evaluated by assigning the value of a to the method's first parameter, then executing the body of the method, and then assigning the methods return value to result. Like the plain assignment in the last example, the "right hand side" must be a subtype of the "left hand side", i.e. this statement can only be valid if typeof(a) ≤ typeof(parameter(method)) and returntype(method) ≤ typeof(result). That is, if method is declared by:

Number[] method(ArrayList<Number> list) { ... }

none of the following expressions will compile:

Integer[] result = method(new ArrayList<Integer>());
Number[] result = method(new ArrayList<Integer>());
Object[] result = method(new ArrayList<Object>());

but

Number[] result = method(new ArrayList<Number>());
Object[] result = method(new ArrayList<Number>());

will.

Another example where subtyping matters is overriding. Consider:

Super sup = new Sub();
Number n = sup.method(1);

where

class Super {
    Number method(Number n) { ... }
}

class Sub extends Super {
    @Override 
    Number method(Number n);
}

Informally, the runtime will rewrite this to:

class Super {
    Number method(Number n) {
        if (this instanceof Sub) {
            return ((Sub) this).method(n);  // *
        } else {
            ... 
        }
    }
}

For the marked line to compile, the method parameter of the overriding method must be a supertype of the method parameter of the overridden method, and the return type a subtype of the overridden method's one. Formally speaking, f(A) = parametertype(method asdeclaredin(A)) must at least be contravariant, and if f(A) = returntype(method asdeclaredin(A)) must at least be covariant.

Note the "at least" above. Those are minimum requirements any reasonable statically type safe object oriented programming language will enforce, but a programming language may elect to be more strict. In the case of Java 1.4, parameter types and method return types must be identical (except for type erasure) when overriding methods, i.e. parametertype(method asdeclaredin(A)) = parametertype(method asdeclaredin(B)) when overriding. Since Java 1.5, covariant return types are permitted when overriding, i.e. the following will compile in Java 1.5, but not in Java 1.4:

class Collection {
    Iterator iterator() { ... }
}

class List extends Collection {
    @Override 
    ListIterator iterator() { ... }
}

I hope I covered everything - or rather, scratched the surface. Still I hope it will help to understand the abstract, but important concept of type variance.

故人的歌 2024-12-28 02:44:01

方差是关于具有不同泛型参数的类之间的关系。他们的关系是我们能够选择他们的原因。

Co 和 Contra 方差是非常合乎逻辑的事情。语言类型系统迫使我们支持现实生活的逻辑。通过例子就很容易理解了。

协方差

例如,您想购买一朵花,并且您所在的城市有两家花店:玫瑰店和雏菊店。

如果你问某人“花店在哪里?”有人告诉你玫瑰店在哪里,可以吗?是的,因为玫瑰是花,如果你想买花,就可以买玫瑰。如果有人回复您雏菊店的地址,这同样适用。
这是协方差的示例:您可以将 A 转换为 A,其中 CA 生成通用值(作为函数的结果返回),则 > 是 B 的子类。协方差与生产者有关。

类型:

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();
}

逆变

例如你想送花给你的女朋友。如果你的女朋友喜欢任何一种花,你能认为她是一个喜欢玫瑰的人,还是一个喜欢雏菊的人?是的,因为如果她喜欢任何一种花,她就会喜欢玫瑰和雏菊。
这是逆变的示例:您可以将 A 转换为 A,其中 C如果 A 使用通用值,则B 的子类。逆变是关于消费者的。

类型:

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());

您可以在 来源

Variance is about relationships between classes with different generics parameters. Their relationships are the reason why we can cast them.

Co and Contra variance are pretty logical things. Language type system forces us to support real life logic. It's easy to understand by example.

Covariance

For instance you want to buy a flower and you have two flowers shop in your city: rose shop and daisy shop.

If you ask someone "where is the flowers shop?" and someone tells you where is rose shop, would it be okay? yes, because rose is a flower, if you want to buy a flower you can buy a rose. The same applies if someone replied you with the address of the daisy shop.
This is example of covariance: you are allowed to cast A<C> to A<B>, where C is a subclass of B, if A produces generic values (returns as a result from the function). Covariance is about producers.

Types:

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();
    }
}

Question is "where is the flower shop?", answer is "rose shop there":

static FlowerShop<? extends Flower> tellMeShopAddress() {
    return new RoseShop();
}

Contravariance

For instance you want to gift flower to your girlfriend. If your girlfriend loves any flower, can you consider her as a person who loves roses, or as a person who loves daisies? yes, because if she loves any flower she would love both rose and daisy.
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 consumes generic value. Contravariance is about consumers.

Types:

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!");
    }

}

You're considering your girlfriend who loves any flower as someone who loves roses, and giving her a rose:

PrettyGirl<? super Rose> girlfriend = new AnyFlowerLover();
girlfriend.takeGift(new Rose());

You can find more at the Source.

離人涙 2024-12-28 02:44:01

采用 java 类型系统,然后是类:

任何类型 T 的对象都可以用 T 子类型的对象替换。

类型变量 - 类方法具有以下后果

class A {
    public S f(U u) { ... }
}

class B extends A {
    @Override
    public T f(V v) { ... }
}

B b = new B();
t = b.f(v);
A a = ...; // Might have type B
s = a.f(u); // and then do V v = u;

可以看出:

  • T 必须是 S 子类型(协变,因为 B 是 A 的子类型)。
  • V 必须是 U 的超类型(逆变,作为逆变继承方向)。

现在,与作为 A 的子类型的 B 相关和相反。可以通过更具体的知识来引入以下更强的类型。在子类型中。

协方差(Java 中可用)很有用,它可以表示在子类型中返回更具体的结果;尤其是当 A=T 且 B=S 时。
逆变表示您已准备好处理更一般的论证。

Taking the java type system, and then classes:

Any object of some type T can be substituted with an object of subtype of T.

TYPE VARIANCE - CLASS METHODS HAVE THE FOLLOWING CONSEQUENCES

class A {
    public S f(U u) { ... }
}

class B extends A {
    @Override
    public T f(V v) { ... }
}

B b = new B();
t = b.f(v);
A a = ...; // Might have type B
s = a.f(u); // and then do V v = u;

It can be seen, that:

  • The T must be subtype S (covariant, as B is subtype of A).
  • The V must be supertype of U (contravariant, as contra inheritance direction).

Now co- and contra- relate to B being subtype of A. The following stronger typings may be introduced with more specific knowledge. In the subtype.

Covariance (available in Java) is useful, to say that one returns a more specific result in the subtype; especially seen when A=T and B=S.
Contravariance says you are prepared to handle a more general argument.

清君侧 2024-12-28 02:44:01
fun main() {
    /** covariance: sub classes can be assigned to parent class
     * If we remove out from ProducerFamily class, then this will give error
     * because `out` denotes that the child classes can be substituted or
     * assignable to parent class */
    val producerFamily: ProducerFamily<Family> = ProducerFamily(Brother())
    producerFamily.get()

    /** contraVariance: parent class can be assigned to sub class
     If we remove in from ConsumerFamily class, then this will give error
     * because `in` denotes that the super classes can be substituted or
     * assignable to child class */
    val consumerFamily: ConsumerFamily<Brother> = ConsumerFamily<Family>()
    consumerFamily.printName(Brother())

    /**
     * Invariance: This type of class requires only same class as per declared type
     * If we pass some different class then it will give error
     * neither sub class nor parent class is acceptable here
     * */
    val inVariance: InVariance<Brother> = InVariance(Brother())
    inVariance.get()
}

open class Family(
    val name: String,
) {
    fun print() {
        println(name)
    }
}

open class Brother : Family("Brother")

open class Sister : Family("Sister")

class ConsumerFamily<in T> {
    fun printName(name: T) {
        println(name.toString())
    }
}

class ProducerFamily<out T>(
    val family: T,
) {
    fun get(): T = family
}

class InVariance<R>(
    val type: R,
) {
    fun get(): R = type
}
fun main() {
    /** covariance: sub classes can be assigned to parent class
     * If we remove out from ProducerFamily class, then this will give error
     * because `out` denotes that the child classes can be substituted or
     * assignable to parent class */
    val producerFamily: ProducerFamily<Family> = ProducerFamily(Brother())
    producerFamily.get()

    /** contraVariance: parent class can be assigned to sub class
     If we remove in from ConsumerFamily class, then this will give error
     * because `in` denotes that the super classes can be substituted or
     * assignable to child class */
    val consumerFamily: ConsumerFamily<Brother> = ConsumerFamily<Family>()
    consumerFamily.printName(Brother())

    /**
     * Invariance: This type of class requires only same class as per declared type
     * If we pass some different class then it will give error
     * neither sub class nor parent class is acceptable here
     * */
    val inVariance: InVariance<Brother> = InVariance(Brother())
    inVariance.get()
}

open class Family(
    val name: String,
) {
    fun print() {
        println(name)
    }
}

open class Brother : Family("Brother")

open class Sister : Family("Sister")

class ConsumerFamily<in T> {
    fun printName(name: T) {
        println(name.toString())
    }
}

class ProducerFamily<out T>(
    val family: T,
) {
    fun get(): T = family
}

class InVariance<R>(
    val type: R,
) {
    fun get(): R = type
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文