Ocasionalmente, ouvi dizer que, com os genéricos, Java não acertou. (referência mais próxima, aqui )
Perdoe minha inexperiência, mas o que os teria melhorado?
Ocasionalmente, ouvi dizer que, com os genéricos, Java não acertou. (referência mais próxima, aqui )
Perdoe minha inexperiência, mas o que os teria melhorado?
Respostas:
Ruim:
List<byte>
realmente é apoiado por um, byte[]
por exemplo, e não é necessário encaixar)Boa:
O maior problema é que os genéricos Java são uma coisa apenas em tempo de compilação e você pode subvertê-los em tempo de execução. C # é elogiado porque faz mais verificações de tempo de execução. Há uma discussão muito boa neste post , e ela leva a outras discussões.
Class
objetos.
O principal problema é que o Java não possui genéricos em tempo de execução. É um recurso de tempo de compilação.
Quando você cria uma classe genérica em Java, eles usam um método chamado "Type Erasure" para realmente remover todos os tipos genéricos da classe e essencialmente substituí-los por Object. A versão milionária dos genéricos é que o compilador simplesmente insere conversões no tipo genérico especificado sempre que ele aparece no corpo do método.
Isso tem muitas desvantagens. Um dos maiores, IMHO, é que você não pode usar reflexão para inspecionar um tipo genérico. Os tipos não são realmente genéricos no código de bytes e, portanto, não podem ser inspecionados como genéricos.
Excelente visão geral das diferenças aqui: http://www.jprl.com/Blog/archive/development/2007/Aug-31.html
(1) leva a um comportamento muito estranho. O melhor exemplo que posso pensar é. Presumir:
public class MyClass<T> {
T getStuff() { ... }
List<String> getOtherStuff() { ... }
}
em seguida, declare duas variáveis:
MyClass<T> m1 = ...
MyClass m2 = ...
Agora ligue getOtherStuff()
:
List<String> list1 = m1.getOtherStuff();
List<String> list2 = m2.getOtherStuff();
O segundo tem seu argumento de tipo genérico removido pelo compilador porque é um tipo bruto (o que significa que o tipo parametrizado não é fornecido), embora não tenha nada a ver com o tipo parametrizado.
Também mencionarei minha declaração favorita do JDK:
public class Enum<T extends Enum<T>>
Além do caractere curinga (que é uma mistura), só acho que os genéricos .Net são melhores.
public class Redundancy<R extends Redundancy<R>>
;)
The expression of type List needs unchecked conversion to conform to List<String>
Enum<T extends Enum<T>>
pode parecer estranho / redundante no início, mas é realmente muito interessante, pelo menos dentro das restrições de Java / seus genéricos. Enums têm um values()
método estático que fornece uma matriz de seus elementos digitados como enum, não Enum
, e esse tipo é determinado pelo parâmetro genérico, o que significa que você deseja Enum<T>
. Obviamente, essa digitação só faz sentido no contexto de um tipo enumerado, e todos os enums são subclasses de Enum
, assim você deseja Enum<T extends Enum>
. No entanto, Java não gosta de misturar tipos brutos com genéricos, portanto, Enum<T extends Enum<T>>
para consistência.
Vou lançar uma opinião realmente controversa. Os genéricos complicam a linguagem e complicam o código. Por exemplo, digamos que eu tenha um mapa que mapeia uma string para uma lista de strings. Nos velhos tempos, eu poderia declarar isso simplesmente como
Map someMap;
Agora, eu tenho que declará-lo como
Map<String, List<String>> someMap;
E cada vez que passo para algum método, tenho que repetir aquela longa declaração mais uma vez. Na minha opinião, toda aquela digitação extra distrai o desenvolvedor e o leva para fora da "zona". Além disso, quando o código é preenchido com muitos fragmentos, às vezes é difícil voltar a ele mais tarde e vasculhar rapidamente todos os fragmentos para encontrar a lógica importante.
Java já tem uma má reputação por ser uma das linguagens mais prolixas de uso comum, e os genéricos apenas aumentam esse problema.
E o que você realmente compra com toda essa verbosidade extra? Quantas vezes você realmente teve problemas quando alguém colocou um Integer em uma coleção que deveria conter Strings, ou quando alguém tentou extrair uma String de uma coleção de Inteiros? Em meus 10 anos de experiência trabalhando na construção de aplicativos Java comerciais, isso nunca foi uma grande fonte de erros. Portanto, não tenho certeza do que você está obtendo com a verbosidade extra. Isso realmente me parece uma bagagem burocrática extra.
Agora vou ficar muito controverso. O que vejo como o maior problema com as coleções em Java 1.4 é a necessidade de typecast em todos os lugares. Eu vejo essas previsões como detalhes extras e detalhados que têm muitos dos mesmos problemas que os genéricos. Então, por exemplo, não posso simplesmente fazer
List someList = someMap.get("some key");
eu tenho que fazer
List someList = (List) someMap.get("some key");
A razão, claro, é que get () retorna um Object que é um supertipo de List. Portanto, a atribuição não pode ser feita sem um typecast. Novamente, pense em quanto essa regra realmente compra você. Pela minha experiência, não muito.
Acho que Java teria ficado muito melhor se 1) não tivesse adicionado genéricos, mas 2) em vez disso, tivesse permitido a conversão implícita de um supertipo em um subtipo. Deixe que conversões incorretas sejam detectadas no tempo de execução. Então eu poderia ter a simplicidade de definir
Map someMap;
e depois fazendo
List someList = someMap.get("some key");
toda a sujeira teria sumido, e eu realmente não acho que introduziria uma grande nova fonte de bugs em meu código.
Outro efeito colateral de serem em tempo de compilação e não em tempo de execução é que você não pode chamar o construtor do tipo genérico. Então você não pode usá-los para implementar uma fábrica genérica ...
public class MyClass {
public T getStuff() {
return new T();
}
}
--jeffk ++
Os genéricos Java são verificados quanto à exatidão em tempo de compilação e, em seguida, todas as informações de tipo são removidas (o processo é chamado de eliminação de tipo . Assim, genérico List<Integer>
será reduzido ao seu tipo bruto , não genérico List
, que pode conter objetos de classe arbitrária.
Isso resulta na possibilidade de inserir objetos arbitrários na lista em tempo de execução, bem como agora é impossível dizer quais tipos foram usados como parâmetros genéricos. Este último, por sua vez, resulta em
ArrayList<Integer> li = new ArrayList<Integer>();
ArrayList<Float> lf = new ArrayList<Float>();
if(li.getClass() == lf.getClass()) // evaluates to true
System.out.println("Equal");
Eu gostaria que isso fosse um wiki para que eu pudesse adicionar a outras pessoas ... mas ...
Problemas:
<
? Estende MyObject >
[], mas não tenho permissão)Ignorando toda a bagunça de eliminação de tipo, os genéricos especificados simplesmente não funcionam.
Isso compila:
List<Integer> x = Collections.emptyList();
Mas este é um erro de sintaxe:
foo(Collections.emptyList());
Onde foo é definido como:
void foo(List<Integer> x) { /* method body not important */ }
Portanto, se um tipo de expressão é verificado depende se ele está sendo atribuído a uma variável local ou a um parâmetro real de uma chamada de método. Quão louco é isso?
A introdução de genéricos em Java foi uma tarefa difícil porque os arquitetos estavam tentando equilibrar funcionalidade, facilidade de uso e compatibilidade com versões anteriores do código legado. Como era de se esperar, acordos tiveram que ser feitos.
Alguns também acham que a implementação de genéricos em Java aumentou a complexidade da linguagem a um nível inaceitável (consulte " Generics Considered Harmful " de Ken Arnold ). As FAQs de Genéricos de Angelika Langer dão uma boa ideia de como as coisas podem se tornar complicadas.
Java não impõe Genéricos em tempo de execução, apenas em tempo de compilação.
Isso significa que você pode fazer coisas interessantes, como adicionar os tipos errados a coleções genéricas.
Os genéricos Java são apenas em tempo de compilação e são compilados em código não genérico. Em C #, o MSIL compilado real é genérico. Isso tem grandes implicações para o desempenho porque o Java ainda faz projeções durante o tempo de execução. Veja aqui para mais informações .
Se você ouvir Java Posse # 279 - Entrevista com Joe Darcy e Alex Buckley , eles falam sobre esse assunto. Isso também leva a uma postagem do blog Neal Gafter intitulada Reified Generics for Java que diz:
Muitas pessoas estão insatisfeitas com as restrições causadas pela maneira como os genéricos são implementados em Java. Especificamente, eles estão descontentes com o fato de os parâmetros de tipo genérico não serem reificados: eles não estão disponíveis no tempo de execução. Os genéricos são implementados usando erasure, em que os parâmetros de tipo genérico são simplesmente removidos no tempo de execução.
Essa postagem do blog faz referência a uma entrada mais antiga, Puzzling Through Erasure: seção de respostas , que enfatizou o ponto sobre a compatibilidade da migração nos requisitos.
O objetivo era fornecer compatibilidade com versões anteriores de código-fonte e código-objeto, e também compatibilidade de migração.