Dev (Back & Front)ARTIGO

Variância em Java

Eu tenho normalmente dificuldade em explicar o
que é a outras pessoas, por isso decidi escrever este artigo.

Variância neste contexto está diretamente 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 {<br />    Object getValue() { return null; }<br />}<br /><br />public class Y extends X {<br />    String getValue() { return null; }<br />}

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).

Covariância

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.

Invariância

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 {<br />    boolean equals(A object) {<br />        //Fantastico, sempre igual<br />        return true;<br />    }<br />}

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 {<br />    void pseudoCovariante(List<? extends A> param) {}<br /><br />    void pseudoContravariante(List<? super A> param) {}<br />}

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 parâmetros invariantes.

Espero ter conseguido explicar os conceitos básicos. Estas noções
aparecem em diversos textos sobre linguagens de programação e é
essencial compreende-las para se entender bem o sistema de
tipos da linguagem Java.

é engenheiro de software focado em Java/Java EE. Trabalhou 6 anos em Portugal e desenvolve actualmente produtos de personalização escaláveis para as maiores operadoras móveis mundiais em Dublin, Irlanda. É co-líder do Grupo de Utilizadores Java Português (PT.JUG) (http://www.java.pt) e escreve regularmente no Blog Zona J (www.zonaj.org).

Ver perfil