Wednesday, September 20, 2006

Arrays, genéricos e o maldito compilador (e uma errata)

Há um tempinho eu postei aqui uma dica que deveria facilitar a construção de listas e maps em Java. A idéia básica era escrever uma classe utilitária com métodos estáticos usando varargs e genéricos que depois seriam importados (via static imports) nas classes clientes. Algo assim:

public class CollectionUtils {
public static List listWith(T... elements) {
return Arrays.asList(elements);
}
...
}

Isso até que funciona direto, mas não posso dizer o mesmo sobre a implementação do mapWith. Este método retorna um java.util.Map criado a partir de uma série de pares chave-valor. Cada par é representado por um objeto da classe Map.Entry<K,V> criado usando outro método estático. Assim um uso do mapWih poderia ser:

Map<NumeroNatural, String> numeros = mapWith(pair(ZERO, "0"), pair(ONE, "1"));

Para programar o mapWith eu usei o mesmo truque do listWith, receber os argumentos (os pares) via varargs:

public static <K, V> Map<K, V> mapWith(Map.Entry<K, V>... entries) {
HashMap<K, V> map = new HashMap<K, V>();
for (Map.Entry<K, V> eachEntry : entries)
map.put(eachEntry.getKey(), eachEntry.getValue());
return map;
}

Essa definição de método compila normalmente, mas todas as chamadas à ele receberão um warning do compilador avisando que um array genérico está sendo criado. "Huh? Mas eu não estou criando porcaria de array nenhum!", foi o que eu respondi para o compilador. Depois de me assegurar que ninguém me viu conversando com o javac.exe, que aliás é um interlocutor bem antipático, pesquisei um pouco e descobri que varargs no Java 5 usa arrays por baixo do pano. "Ok, mas qual é o problema de criar um array de tipo genérico?". Bom, vamos por partes, como diria o Leibniz. Primeiro precisamos lembrar que arrays são covariantes no tipo do seu elemento; veja:

String[] sa = new String[10];
Object[] oa = sa;
oa[0] = new Integer(5);

Esse trecho de código compila, mas a última linha atira uma exceção (ArrayStoreException) em tempo de execução. Ou seja, a VM precisa testar se o tipo do array é adequado ao tipo de um objeto sendo inserido nele. Agora, pense nesse trecho:

Class<String>[] ca = new Class<String>[10];
Object[] oa = ca;
ca[0] = new Integer(8);

Aqui, o compilador reclama já na primeira linha que não consegue criar um array de Class<String>. E ele não consegue por um detalhe da implementação de generics no Java: a técnica escolhida para especificar tipos genéricos, chamada "Erasure", remove as informações sobre os parâmetros de tipo (as coisas dentro de <>) ao produzir o bytecode. Como a linguagem não teria informação suficiente para fazer as verificações necessárias quando da inserção de elementos no array, a solução adotada é simplesmente proibir arrays de tipos genéricos.

Agora vou fazer como todo bom mau programador e botar a culpa na linguagem por um bug que eu escrevi. Começando pela escolha de implementar generics via erasure, que significou sacrificar naturalidade em nome da tal "migration compatibility". Volta e meia acontece de eu escrever algum statement que parece perfeitamente inocente e o compilador me chama de burro (ou quase isso). Outra decisão que não me agradou foi a de usar arrays como base dos varargs, em vez de algo menos capenga como Iterable ou Collection. E minha última reclamação é sobre a covariância dos arrays, que enfraquece a tipagem estática a troco de nada (alguém sabe de um bom caso de uso para arrays covariantes?).

4 comments:

tautologico said...

Eu acho que a idéia de permitir arrays covariantes era a seguinte: você tem uma coleção de widgets, todos derivados de uma classe Widget, e essa coleção está em um array Widget[]. Aí para mandar todos os widgets se desenharem é só iterar no array chamando o método draw() em cada elemento, sendo draw() obviamente um método da classe base.

Pode não ser a melhor forma de fazer isso, mas acho que é um tipo de caso que foi considerado para tomar essa decisão. O interessante é que arrays covariantes não têm nenhum problema se o array for imutável (e a situação é dual para arrays contravariantes). Tem algo sobre isso no Types and Programming Languages de Pierce.

tautologico said...

Ah sim, faltou dizer que erasure sucks. Os genéricos em .net são melhores.

opbest said...


The the very next time I read a blog, Hopefully which it doesnt disappoint me around brussels.
What i’m saying is, It was my choice to read, but I just thought youd have some thing interesting to convey. All I hear can be a couple of whining about something you could fix in the event you werent too busy in search of attention.

Try to check my webpage - 오피사이트

(freaky)

johnrock said...

Nice Post thank you very much for sharing such helpful information about "Arrays, Generics, and the Damned Compiler (and an Errata)" and will definitely save and revisit your site and I have bookmarked to check out new things from your post. Try to check out my website: descriptive essay help.