Ковариантность, инвариантность и контравариантность объясняются простым английским языком? - PullRequest
105 голосов
/ 13 декабря 2011

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

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

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

Ответы [ 2 ]

253 голосов
/ 13 декабря 2011

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

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

В глубине души эти термины описывают, как на отношение подтипа влияют преобразования типов.То есть, если 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 последнее верно, мы говорим (несколько неофициально), что generics являются инвариантными.

Другой пример.Пусть 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() { ... }
}

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

11 голосов
/ 13 декабря 2011

Взяв систему типов Java, а затем классы:

Любой объект некоторого типа T может быть заменен объектом подтипа T.

ТИП VARIANCE- КЛАССОВЫЕ МЕТОДЫ ИМЕЮТ СЛЕДУЮЩИЕ ПОСЛЕДСТВИЯ

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.Контравариантность говорит, что вы готовы принять более общий аргумент.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...