Объяснение ковариации, инвариантности и контравариантности простым языком?

113

Сегодня я прочитал несколько статей о ковариантности, контравариантности (и инвариантности) в Java. Я читал статьи в Википедии на английском и немецком языках, а также некоторые другие сообщения в блогах и статьи IBM.

Но я все еще немного не понимаю, о чем они? Некоторые говорят, что это связано с отношениями между типами и подтипами, некоторые говорят о преобразовании типов, а некоторые говорят, что это используется для определения того, переопределяется метод или перегружен.

Поэтому я ищу простое объяснение на простом английском языке, которое покажет новичку, что такое ковариация и контравариантность (и инвариантность). Плюс к простому примеру.

tzrm
источник
Пожалуйста, обратитесь к этому посту, он может быть вам полезен: stackoverflow.com/q/2501023/218717
Франсиско Альварадо,
3
Возможно, лучше вопрос типа обмена стеками программиста. Если вы публикуете там пост, подумайте о том, чтобы указать, что именно вы понимаете и что конкретно вас смущает, потому что прямо сейчас вы просите кого-нибудь переписать для вас целое руководство.
Судно на воздушной подушке Full Of Eels

Ответы:

289

Некоторые говорят, что это касается взаимосвязи между типами и подтипами, другие говорят, что это касается преобразования типов, а другие говорят, что это используется для определения того, перезаписан метод или перегружен.

Все вышеперечисленное.

По сути, эти термины описывают, как на отношение подтипов влияют преобразования типов. То есть, если Aи Bявляются типами, fявляется преобразованием типа и ≤ отношение подтипа (т.е. A ≤ Bозначает, что Aэто подтип B), мы имеем

  • fковариантно, если A ≤ Bследует, чтоf(A) ≤ f(B)
  • fконтравариантно, если A ≤ Bследует, чтоf(B) ≤ f(A)
  • f инвариантен, если ни одно из вышеперечисленных условий не выполняется

Рассмотрим пример. Пусть f(A) = List<A>где Listуказано

class List<T> { ... } 

Является fковариантны, контравариантен или инвариант? Ковариантный будет означать , что List<String>является подтипом List<Object>, контравариантно что List<Object>является подтипом List<String>и инвариантно , что ни является подтип других, т.е. List<String>и List<Object>являются неконвертируемыми типами. В 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первому параметру метода, затем выполнения тела метода и последующего присвоения возвращаемого значения метода result. Подобно простому присваиванию в последнем примере, «правая часть» должна быть подтипом «левой стороны», то есть этот оператор может быть действительным, только если 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);

где

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))должен быть как минимум контравариантным, а если 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() { ... }
}

Надеюсь, я все осветил - точнее, поцарапал поверхность. Тем не менее я надеюсь, что это поможет понять абстрактную, но важную концепцию вариативности типов.

меритон
источник
1
Кроме того, начиная с Java 1.5, при переопределении разрешены контравариантные типы аргументов. Я думаю, ты это пропустил.
Брайан Гордон
13
Они? Я просто попробовал его в eclipse, и компилятор решил, что я имел в виду перегрузку, а не переопределение, и отклонил код, когда я поместил аннотацию @Override в метод подкласса. Есть ли у вас доказательства того, что Java поддерживает контравариантные типы аргументов?
меритон
1
Ах ты прав. Я кому-то поверил, не проверив сам.
Брайан Гордон
1
Я прочитал много документации и посмотрел несколько разговоров на эту тему, но это, безусловно, лучшее объяснение. Спасибо большое.
minzchickenflavor
1
+1 за то, что он абсолютно луман и прост с A ≤ B. Эта запись делает вещи намного более простыми и значимыми. Хорошее чтение ...
Romeo Sierra
12

Взяв систему типов 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 ( контравариантным , как противоположное направление наследования).

Теперь соотнесите B с подтипом A. Следующие более строгие типы могут быть введены с более конкретными знаниями. В подтипе.

Ковариация (доступна в Java) полезна, чтобы сказать, что в подтипе возвращается более конкретный результат; особенно это видно, когда A = T и B = S. Контравариантность говорит о том, что вы готовы к более общему аргументу.

Юп Эгген
источник
9

Дисперсия - это отношения между классами с разными параметрами дженериков. Их отношения - причина, по которой мы можем использовать их.

Дисперсия Co и Contra - довольно логичные вещи. Система языковых типов заставляет нас поддерживать логику реальной жизни. Это легко понять на примере.

ковариации

Например, вы хотите купить цветок, и в вашем городе есть два цветочных магазина: магазин роз и магазин ромашек.

Если вы спросите кого-нибудь, "где находится цветочный магазин?" и кто-нибудь скажет вам, где находится магазин роз, можно? да, потому что роза - это цветок, если вы хотите купить цветок, вы можете купить розу. То же самое касается и тех, кто ответил вам адресом магазина ромашек. Это пример ковариации : вам разрешено приведение A<C>к A<B>, где Cявляется подклассом 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();
}

Контравариантность

Например, вы хотите подарить девушке цветок. Если ваша девушка любит любой цветок, можете ли вы считать ее человеком, который любит розы, или человеком, который любит ромашки? да, потому что если она любит любой цветок, она полюбит и розу, и ромашку. Это пример контравариантности : вам разрешено приведение A<B>к A<C>, где Cявляется подклассом B, если 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());

Вы можете найти больше в источнике .

Вадзим В.
источник
@ Питер, спасибо, это справедливый вопрос. Инвариантность - это когда нет отношений между классами с разными универсальными параметрами, то есть вы не можете преобразовать A <B> в A <C> независимо от отношения между B и C.
VadzimV