Covariância, Invariância e Contravariância explicadas em inglês simples?

113

Hoje, eu li alguns artigos sobre Covariância, Contravariância (e Invariância) em Java. Eu li o artigo da Wikipedia em inglês e alemão e algumas outras postagens de blog e artigos da IBM.

Mas ainda estou um pouco confuso sobre o que exatamente se trata? Alguns dizem que é sobre relacionamento entre tipos e subtipos, alguns dizem que é sobre conversão de tipo e alguns dizem que é usado para decidir se um método é sobrescrito ou sobrecarregado.

Portanto, estou procurando uma explicação fácil em inglês simples, que mostre a um iniciante o que é Covariância e Contravariância (e Invariância). Ponto positivo para um exemplo fácil.

tzrm
fonte
Consulte esta postagem, ela pode ser útil para você: stackoverflow.com/q/2501023/218717
Francisco Alvarado
3
Talvez seja melhor uma questão de tipo de troca de pilha do programador. Se você postar lá, considere declarar apenas o que você entende, e o que especificamente o confunde, porque agora você está pedindo a alguém para reescrever um tutorial inteiro para você.
Hovercraft Full Of Eels

Respostas:

288

Alguns dizem que é sobre relacionamento entre tipos e subtipos, outros dizem que é sobre conversão de tipo e outros dizem que é usado para decidir se um método é sobrescrito ou sobrecarregado.

Tudo acima.

No fundo, esses termos descrevem como a relação de subtipo é afetada pelas transformações de tipo. Ou seja, se Ae Bsão tipos, fé uma transformação de tipo, e ≤ a relação de subtipo (ou seja, A ≤ Bsignifica que Aé um subtipo de B), temos

  • fé covariante se A ≤ Bimplica quef(A) ≤ f(B)
  • fé contravariante se A ≤ Bimplica quef(B) ≤ f(A)
  • f é invariante se nenhuma das opções acima for válida

Vamos considerar um exemplo. Deixe f(A) = List<A>onde Listé declarado por

class List<T> { ... } 

É fcovariante, contravariante ou invariante? Covariante significaria que a List<String>é um subtipo de List<Object>, contravariante que a List<Object>é um subtipo de List<String>e invariante que nenhum é um subtipo do outro, ou seja, List<String>e List<Object>são tipos inconversíveis. Em Java, o último é verdade, dizemos (um tanto informalmente) que os genéricos são invariantes.

Outro exemplo. Deixe f(A) = A[]. É fcovariante, contravariante ou invariante? Ou seja, String [] é um subtipo de Object [], Object [] um subtipo de String [], ou nenhum é um subtipo do outro? (Resposta: Em Java, os arrays são covariantes)

Isso ainda era bastante abstrato. Para tornar isso mais concreto, vamos examinar quais operações em Java são definidas em termos da relação de subtipo. O exemplo mais simples é a atribuição. A declaração

x = y;

irá compilar apenas se typeof(y) ≤ typeof(x). Ou seja, acabamos de saber que as declarações

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

não vai compilar em Java, mas

Object[] objects = new String[1];

vai.

Outro exemplo onde a relação de subtipo é importante é uma expressão de invocação de método:

result = method(a);

Falando informalmente, essa instrução é avaliada atribuindo o valor de aao primeiro parâmetro do método, executando o corpo do método e atribuindo o valor de retorno do método a result. Como a atribuição simples no último exemplo, o "lado direito" deve ser um subtipo do "lado esquerdo", ou seja, esta instrução só pode ser válida se typeof(a) ≤ typeof(parameter(method))e returntype(method) ≤ typeof(result). Ou seja, se o método for declarado por:

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

nenhuma das seguintes expressões irá compilar:

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

mas

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

vai.

Outro exemplo em que a subtipagem é importante é a substituição. Considerar:

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

Onde

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

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

Informalmente, o tempo de execução irá reescrever isso para:

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

Para a linha marcada para compilar, o parâmetro do método do método sobrescrito deve ser um supertipo do parâmetro do método do método sobrescrito, e o tipo de retorno um subtipo do método sobrescrito. Falando formalmente, f(A) = parametertype(method asdeclaredin(A))deve ser pelo menos contravariante, e se f(A) = returntype(method asdeclaredin(A))deve ser pelo menos covariante.

Observe o "pelo menos" acima. Esses são requisitos mínimos que qualquer linguagem de programação orientada a objetos segura de tipo estaticamente razoável irá impor, mas uma linguagem de programação pode optar por ser mais estrita. No caso do Java 1.4, os tipos de parâmetro e tipos de retorno de método devem ser idênticos (exceto para eliminação de tipo) ao substituir métodos, ou seja, parametertype(method asdeclaredin(A)) = parametertype(method asdeclaredin(B))ao substituir. Desde Java 1.5, os tipos de retorno covariant são permitidos durante a substituição, ou seja, o seguinte será compilado em Java 1.5, mas não em Java 1.4:

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

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

Espero ter coberto tudo - ou melhor, arranhado a superfície. Ainda assim, espero que ajude a entender o conceito abstrato, mas importante, de variância de tipo.

Meriton
fonte
1
Além disso, como os tipos de argumento contravariantes do Java 1.5 são permitidos durante a substituição. Eu acho que você perdeu isso.
Brian Gordon
13
São eles? Eu apenas tentei no Eclipse, e o compilador pensou que eu pretendia sobrecarregar em vez de substituir, e rejeitou o código quando coloquei uma anotação @Override no método da subclasse. Você tem alguma evidência para sua afirmação de que Java oferece suporte a tipos de argumento contravariantes?
meriton
1
Ah, você está certo. Eu acreditei em alguém sem verificar eu mesma.
Brian Gordon
1
Eu li muita documentação e assisti a algumas palestras sobre esse assunto, mas esta é de longe a melhor explicação. Muito obrigado.
minzchickenflavor
1
1 por ser absolutamente leman e simples com A ≤ B. Essa notação torna as coisas muito mais simples e significativas. Boa leitura ...
Romeo Sierra
12

Pegando o sistema de tipo java e depois as classes:

Qualquer objeto de algum tipo T pode ser substituído por um objeto do subtipo de T.

VARIÂNCIA DE TIPO - OS MÉTODOS DE CLASSE TÊM AS SEGUINTES CONSEQUÊNCIAS

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;

Pode ser visto que:

  • OT deve ser subtipo S ( covariante, já que B é o subtipo de A ).
  • O V deve ser supertipo de U ( contravariante , como contra direção de herança).

Agora co- e contra- relacionam-se com B sendo o subtipo de A. As seguintes tipificações mais fortes podem ser introduzidas com um conhecimento mais específico. No subtipo.

Covariância (disponível em Java) é útil, para dizer que se retorna um resultado mais específico no subtipo; especialmente visto quando A = T e B = S. A contravariância diz que você está preparado para lidar com um argumento mais geral.

Joop Eggen
fonte
9

A variância diz respeito às relações entre classes com diferentes parâmetros genéricos. Seus relacionamentos são a razão pela qual podemos lançá-los.

Variância Co e Contra são coisas bastante lógicas. O sistema de tipo de linguagem nos força a apoiar a lógica da vida real. É fácil entender por exemplo.

Covariância

Por exemplo, você quer comprar uma flor e tem duas floriculturas em sua cidade: uma loja de rosas e uma loja de margaridas.

Se você perguntar a alguém "onde fica a loja de flores?" e alguém lhe disser onde fica a rose shop, tudo bem? sim, porque rosa é uma flor, se quiseres comprar uma flor podes comprar uma rosa. O mesmo se aplica se alguém lhe responder com o endereço da loja de margaridas. Este é um exemplo de covariância : você pode lançar A<C>para A<B>, onde Cé uma subclasse de B, se Aproduz valores genéricos (retorna como resultado da função). A covariância tem a ver com produtores.

Tipos:

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

A pergunta é "onde fica a floricultura?", A resposta é "loja de flores lá":

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

Contravariância

Por exemplo, você deseja presentear sua namorada com flores. Se sua namorada adora qualquer flor, você pode considerá-la uma pessoa que adora rosas ou uma pessoa que adora margaridas? sim, porque se ela ama qualquer flor ela amaria rosa e margarida. Este é um exemplo de contravariância : você tem permissão para lançar A<B>para A<C>, onde Cé subclasse de B, se Aconsome valor genérico. A contravariância diz respeito aos consumidores.

Tipos:

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

}

Você está considerando sua namorada que ama qualquer flor como alguém que ama rosas, e está dando a ela uma rosa:

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

Você pode encontrar mais na Fonte .

VadzimV
fonte
@ Peter, obrigado, é um ponto justo. Invariância é quando não há relacionamentos entre classes com parâmetros genéricos diferentes, ou seja, você não pode converter A <B> em A <C> qualquer que seja a relação entre B e C.
VadzimV