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

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

lin liyi said...

A hublot bingo bango beerfest watch. if hublot was calling the prices, this replica watches sale price tag would have one more zero. Love their movements but hate the rest as a general rule. And this one totally replica watches uk outside of the movement finishing. Thanks for a brutally honest review David. But I do find it strange that you would assess such an interesting replica watches so negatively. To be frank, it much better than anything you have ever produced. However, to call vertical clutch disengagement any hublot replica sale of a second the accuracy of the rolex watches store is balderdash sir. The world is full of other watches. And I know that my watches have a niche appeal. The thing I always forget the date and I must have it in a watch. While the rolex replica sale side is good somehow all the gold is just too much. To quote a high class movie. I should be much more in love with but just am Not, and thisbis one of them. I love rolex watches store, especially bizarre ones. This one fits that bill. I love fine watchmaking. But there replica watches sale too many misses here for a proper love affair.