Variância em sistemas de tipos de linguagens orientas a objectos é uma noção simples e com a qual nos deparamos todos os dias mas é complexa de explicar. Eu tenho normalmente dificuldade em explicar o que é a outras pessoas por isso decidi escrever este post e assim mando-lhes apenas o link
Variância neste contexto está directamente relacionada com herança. É relevante para compreender como é feito matching de parametros de métodos, resultados de retorno, tipos genéricos e outros casos.
Existem três tipos de variância: invariância, covariância e contravariância.
- Covariância
Suponhamos o seguinte caso:
public class X { Object getValue() { return null; } } public class Y extends X { String getValue() { return null; } }
A covariância neste caso está no tipo de retorno. Significa que se a classe Y é mais específica que X (Y< X) então qualquer método de Y que faça override a um método de X tem de retornar um tipo igual ou mais específico. Neste caso, o tipo de retorno do método que faz override é String, que é mais específico que Object.
Diz-se, pois, que os tipos de retornos em Java são covariantes (desde a versão 1.5).
Contravariância é efectivamente o inverso de covariância. Significa que se a classe Y é mais específica que X (Y<X) então qualquer método de Y que faça override a um método de X tem de retornar um tipo igual ou mais genérico.
Em Java não há contravariância e overriding de métodos é sempre invariante … que passo a explicar de seguida. É no entanto possível ter contravariância usando wildcards de generics.
Em Java overriding de métodos é invariante, ou seja, para se redefinir um método numa subclasse, os parâmetros têm de ser exactamente do mesmo tipo do seu ancestror.
Este comportamento é muitas vezes descurado, veja-se o seguinte exemplo:
public class A { boolean equals(A object) { //Fantastico, sempre igual return true; } }
Como o overriding é invariante, não estamos na realidade a fazer override do método boolean equals(Object o) mas sim overload, adicionando um novo método com outra assinatura. O pior é que passa completamente despercebido.
A solução desde Java 5 é adicionar a anotação @Override que vai permitir que o compilador detecte estes casos e informe que não estamos realmente a redifinir um método. Aliás, todos os IDEs decentes sugerem que se adicione esta anotação.
Uma nota sobre Generics
As noções de covariância e contravariância são importantes de ter presente quando definindo classes tipificadas com generics, especialmente quando se usam as wildcards para explicitamente definir relações entre tipos. Não estamos a definir realmente relações hierárquicas entre tipos, pois não há a relação de herança entre classes genéricas mas estamos a definir relações entre os tipos que parametrizam essa classe, se me consigo fazer entender.
Por exemplo:
public class Teste { void pseudoCovariante(List<? extends A> param) {} void pseudoContravariante(List<? super A> param) {} }
Aqui definimos dois métodos para dar exemplos de como dotar classes tipificadas de noções de contravariância e covariância, já que se não usarmos wildcards temos parametros invariantes.
Espero ter conseguido explicar os conceitos básicos. Estas noções aparecem em diversos textos sobre linguagens de programação e é essencial compreender estas noções para se entender bem o sistema de tipos da linguagem Java.