O que há de errado com os genéricos de Java? [fechadas]


49

Já vi várias vezes neste site postagens que desacreditam a implementação de genéricos em Java. Agora, posso dizer honestamente que não tive problemas com o uso deles. No entanto, eu não tentei fazer uma classe genérica sozinho. Então, quais são seus problemas com o suporte genérico do Java?




como Clinton Begin (o "cara do iBatis") disse: "eles não funcionam ..." #
30511

Respostas:


54

A implementação genérica do Java usa apagamento de tipo . Isso significa que suas coleções genéricas fortemente tipadas são realmente do tipo Objectem tempo de execução. Isso tem algumas considerações de desempenho, pois significa que os tipos primitivos devem ser colocados em caixas quando adicionados a uma coleção genérica. É claro que os benefícios da correção do tipo em tempo de compilação superam a tolice geral do apagamento do tipo e o foco obsessivo na compatibilidade com versões anteriores.


4
Só para adicionar, por causa do apagamento do tipo, não é possível recuperar informações genéricas em tempo de execução, a menos que você use algo como TypeLiteral google-guice.googlecode.com/svn/trunk/javadoc/com/google/inject/…
Jeremy Heiler

43
Significando quenew ArrayList<String>.getClass() == new ArrayList<Integer>.getClass()
Observe a si mesmo - pense em um nome

9
@ Job não, não. E o C ++ usa uma abordagem completamente diferente da metaprogramação chamada modelos. Ele usa digitação de pato e você pode fazer coisas úteis, template<T> T add(T v1, T v2) { return v1->add(v2); }e aí você tem uma maneira verdadeiramente genérica de criar uma função que addé duas coisas, não importa quais sejam essas coisas, elas apenas precisam ter um método nomeado add()com um parâmetro.
Trinidad

7
@ Job é gerado código, de fato. Os modelos destinam-se a substituir o pré-processador C e, para isso, devem ser capazes de fazer alguns truques muito inteligentes e, por si só, são uma linguagem completa de Turing. Os genéricos Java são apenas açúcar para contêineres de tipo seguro e os C # genéricos são melhores, mas ainda são um modelo de C ++ para pessoas pobres.
Trinidad

3
@Job AFAIK, os genéricos Java não geram uma classe para cada novo tipo genérico, apenas adiciona previsões ao código que usa métodos genéricos, portanto, na verdade, não é meta-programação IMO. Os modelos de C # geram novo código para cada tipo genérico, ou seja, se você usar Lista <int> e Lista <double> no mundo do C #, geraria código para cada um deles. Em comparação com os modelos, os C # genéricos ainda precisam saber qual o tipo que você pode alimentá-lo. Você não pode implementar o Addexemplo simples que eu dei, não há como saber de antemão quais classes podem ser aplicadas, o que é uma desvantagem.
Trinidad

26

Observe que as respostas que já foram fornecidas se concentram na combinação de Java-the-language, JVM e Java Class Library.

Não há nada de errado com os genéricos de Java no que diz respeito à linguagem Java. Conforme descrito em C # vs Java genéricos , os genéricos de Java são muito bons no nível 1 da linguagem .

O que é subótimo é que a JVM não suporta diretamente genéricos, o que tem várias consequências importantes:

  • as partes relacionadas à reflexão da Biblioteca de classes não podem expor as informações completas do tipo que estavam disponíveis no Java-the-language
  • alguma penalidade de desempenho está envolvida

Suponho que a distinção que estou fazendo seja pedante, pois o Java-the-language é onipresentemente compilado com a JVM como destino e a Java Class Library como a biblioteca principal.

1 Com a possível exceção dos curingas, que se acredita tornar a inferência de tipo indecidível no caso geral. Essa é uma grande diferença entre os genéricos de C # e Java que não é mencionada com muita frequência. Obrigado, antimônio.


14
A JVM não precisa suportar genéricos. Seria o mesmo que dizer que o C ++ não tem modelos porque a máquina real ix86 não suporta modelos, o que é falso. O problema é a abordagem adotada pelo compilador para implementar tipos genéricos. E, novamente, os modelos C ++ não envolvem penalidade no tempo de execução, não há reflexão sendo feita, acho que a única razão pela qual seria necessário fazer em Java é apenas um design de linguagem ruim.
Trinidad

2
@ Trinidad, mas eu não disse que Java não tem genéricos, então não vejo como é a mesma coisa. E sim, a JVM não precisa apoiá-los, assim como o C ++ não precisa otimizar o código. Mas não otimizá-lo certamente contaria como "algo errado".
Roman Starkov

3
O que eu argumentei é que não há necessidade de a JVM ter suporte direto aos genéricos para poder usar a reflexão sobre eles. Outros o fizeram para que isso possa ser feito, o problema é que o Java não gera novas classes a partir de genéricos, o que não requer reificação.
Trinidad

4
@Trinidad: Em C ++, assumindo que o compilador tenha tempo e memória suficientes, os modelos podem ser usados ​​para fazer qualquer coisa e tudo o que pode ser feito com informações disponíveis no momento da compilação (já que a compilação de modelos é completa em Turing), mas há não há como criar modelos usando informações que não estão disponíveis antes da execução do programa. Em .net, por outro lado, é possível que um programa crie tipos com base na entrada; seria bastante fácil escrever um programa em que o número de tipos diferentes que poderiam ser criados excede o número de elétrons no universo.
Supercat 22/12 /

2
@Trinidad: Obviamente, nenhuma execução isolada do programa poderia criar todos esses tipos (ou, de fato, algo além de uma fração infinitesimal deles), mas o ponto é que não é necessário. Ele só precisa criar tipos que são realmente usados. Alguém poderia ter um programa que pudesse aceitar qualquer número arbitrário (por exemplo 8675309) e a partir dele criar um tipo exclusivo para esse número (por exemplo Z8<Z6<Z7<Z5<Z3<Z0<Z9>>>>>>>), que teria membros diferentes de qualquer outro tipo. Em C ++, todos os tipos que poderiam ser gerados a partir de qualquer entrada teriam que ser produzidos em tempo de compilação.
Supercat 22/10

13

A crítica usual é a falta de reificação. Isto é, objetos em tempo de execução não contêm informações sobre seus argumentos genéricos (embora as informações ainda estejam presentes em campos, métodos, construtores e classes e interfaces estendidas). Isso significa que você pode lançar, digamos, ArrayList<String>para List<File>. O compilador fornecerá avisos, mas também alertará se você ArrayList<String>atribuir uma Objectreferência a uma referência e depois convertê-la em List<String>. As vantagens são que, com os genéricos, você provavelmente não deveria fazer a transmissão, o desempenho é melhor sem os dados desnecessários e, é claro, a compatibilidade com versões anteriores.

Algumas pessoas reclamam que você não pode sobrecarregar com base em argumentos genéricos ( void fn(Set<String>)e void fn(Set<File>)). Você precisa usar nomes de métodos melhores. Observe que essa sobrecarga não exigiria reificação, pois a sobrecarga é um problema estático em tempo de compilação.

Tipos primitivos não funcionam com genéricos.

Curingas e limites são bastante complicados. Eles são muito úteis. Se Java preferisse interfaces de imutabilidade e de dizer-não-perguntar, os genéricos do lado da declaração e não do lado do uso seriam mais apropriados.


"Observe que essa sobrecarga não exigiria reificação, pois a sobrecarga é um problema estático em tempo de compilação." Não seria? Como isso funcionaria sem reificação? A JVM não precisaria saber em tempo de execução que tipo de conjunto você possui, para saber qual método chamar?
MatrixFrog

2
@MatrixFrog Não. Como eu disse, sobrecarregar é um problema de tempo de compilação. O compilador usa o tipo estático da expressão para selecionar uma sobrecarga específica, que é o método gravado no arquivo de classe. Se você tiver Object cs = new char[] { 'H', 'i' }; System.out.println(cs);um absurdo impresso. Mude o tipo de cspara char[]e você receberá Hi.
Tom Hawtin - defina

10

Os genéricos Java são ruins porque você não pode fazer o seguinte:

public class AsyncAdapter<Parser,Adapter> extends AsyncTask<String,Integer,Adapter> {
    proptected Adapter doInBackground(String... keywords) {
      Parser p = new Parser(keywords[0]); // this is an error
      /* some more stuff I was hoping for but couldn't do because
         the compiler wouldn't let me
      */
    }
}

Você não precisaria abater todas as classes no código acima para fazê-lo funcionar como deveria se os genéricos fossem realmente parâmetros de classe genéricos.


Você não precisa massacrar todas as aulas. Você só precisa adicionar uma classe de fábrica adequada e injetá-la no AsyncAdapter. (E fundamentalmente não se trata de genéricos, mas do fato de que os construtores não são herdados em Java).
Peter Taylor

13
@ PeterTaylor: Uma classe de fábrica adequada apenas coloca todo o clichê em alguma outra bagunça fora da vista e ainda não resolve o problema. Agora, toda vez que eu quiser instanciar uma nova instância, Parserprecisarei tornar o código da fábrica ainda mais complicado. Enquanto se "genérico" realmente significava genérico, não haveria necessidade de fábricas e todas as outras bobagens que se chamam padrões de design. É uma das muitas razões pelas quais prefiro trabalhar em linguagens dinâmicas.
Davidk01 30/10

2
Às vezes, semelhante em C ++, onde você não pode passar o endereço de um construtor, ao usar modelos + virtuais - você pode simplesmente usar um functor de fábrica.
Mark K Cowan

Java é péssimo porque você não pode escrever "protegido" antes do nome de um método? Pobre voce. Atenha-se a idiomas dinâmicos. E seja especialmente cauteloso com Haskell.
fdreger 10/09

5
  • avisos do compilador para a falta de parâmetros genéricos que não servem para nada tornam a linguagem sem sentido, por exemplo public String getName(Class<?> klazz){ return klazz.getName();}

  • Os genéricos não funcionam bem com matrizes

  • As informações de tipo perdido tornam a reflexão uma bagunça de fundição e fita adesiva.


Fico aborrecido quando recebo um aviso de uso em HashMapvez de HashMap<String, String>.
Michael K

11
@ Michael, mas que na verdade deve ser usado com genéricos ...
alternativa

O problema do array vai para a reifiabilidade. Consulte o livro Java Generics (Alligator) para obter uma explicação.
Ncmathsadist

5

Eu acho que outras respostas disseram isso em algum grau, mas não muito claramente. Um dos problemas com os genéricos é perder os tipos genéricos durante o processo de reflexão. Então, por exemplo:

List<String> arr = new ArrayList<String>();
assertTrue( ArrayList.class, arr.getClass() );
TypeVarible[] types = arr.getClass().getTypedVariables();

Infelizmente, os tipos retornados não podem dizer que os tipos genéricos de arr são String. É uma diferença sutil, mas é importante. Como o arr é criado no tempo de execução, os tipos genéricos são apagados no tempo de execução, portanto você não pode descobrir isso. Como alguns afirmam, ArrayList<Integer>parece o mesmo ArrayList<String>do ponto de vista da reflexão.

Isso pode não importar para o usuário do Generics, mas digamos que desejássemos criar uma estrutura sofisticada que usasse a reflexão para descobrir coisas sofisticadas sobre como o usuário declarou os tipos genéricos concretos de uma instância.

Factory<MySpecialObject> factory = new Factory<MySpecialObject>();
MySpecialObject obj = factory.create();

Digamos que queríamos que uma fábrica genérica criasse uma instância, MySpecialObjectporque esse é o tipo genérico concreto que declaramos para essa instância. A classe Factory não pode se interrogar para descobrir o tipo concreto declarado para esta instância porque o Java os apagou.

Nos genéricos do .Net, você pode fazer isso porque, em tempo de execução, o objeto sabe que são tipos genéricos, porque o compilador o inseriu no binário. Com apagamentos, o Java não pode fazer isso.


1

Eu poderia dizer algumas coisas boas sobre genéricos, mas essa não era a questão. Eu poderia reclamar que eles não estão disponíveis em tempo de execução e não funcionam com matrizes, mas isso foi mencionado.

Um grande aborrecimento psicológico: ocasionalmente, encontro situações em que os genéricos não podem funcionar. (Matrizes são o exemplo mais simples.) E não consigo descobrir se os genéricos não podem fazer o trabalho ou se sou apenas estúpido. Eu odeio isso. Algo como genéricos deve funcionar sempre. Todas as vezes que não consigo fazer o que quero usando a linguagem Java, sei que o problema sou eu e sei que, se continuar pressionando, chegarei lá eventualmente. Com os genéricos, se eu me tornar persistente demais, posso perder muito tempo.

Mas o problema real é que os genéricos adicionam muita complexidade para pouco ganho. Nos casos mais simples, isso pode me impedir de adicionar uma maçã a uma lista que contém carros. Bem. Mas, sem genéricos, esse erro geraria uma ClassCastException realmente rápida no tempo de execução, com pouco tempo perdido. Se eu adicionar um carro com uma cadeira de criança com uma criança, preciso de um aviso em tempo de compilação de que a lista é apenas para carros com cadeiras de criança que contêm chimpanzés de bebê? Uma lista de instâncias simples de Objeto começa a parecer uma boa ideia.

O código gerado pode ter muitas palavras e caracteres, ocupar muito espaço extra e demorar muito mais para ler. Eu posso gastar muito tempo fazendo com que todo esse código extra funcione corretamente. Quando isso acontece, me sinto incrivelmente inteligente. Também perdi várias horas ou mais do meu próprio tempo e tenho que me perguntar se mais alguém será capaz de descobrir o código. Eu gostaria de poder entregá-lo para manutenção a pessoas que são menos inteligentes que eu ou que têm menos tempo a perder.

Por outro lado (fiquei um pouco confuso e sinto a necessidade de fornecer algum equilíbrio), é bom ao usar coleções e mapas simples para adicionar, colocar e ser verificados quando escritos, e geralmente não adiciona muito complexidade ao código ( se alguém outra pessoa escreve a coleção ou mapa) . E Java é melhor nisso que C #. Parece que nenhuma das coleções de C # que eu quero usar jamais manipula genéricos. (Admito que tenho gostos estranhos em coleções.)


Bom discurso retórico. No entanto, existem muitas bibliotecas / estruturas que poderiam existir sem genéricos, por exemplo, Guice, Gson, Hibernate. E os genéricos não são tão difíceis assim que você se acostuma. Digitar e ler os argumentos de tipo é uma verdadeira PITA; aqui val ajuda se você pode usar.
Maaartinus 23/05

11
@maaartinus: Escreveu isso há um tempo atrás. O OP também pediu "problemas". Acho os genéricos extremamente úteis e gosto muito deles - muito mais agora do que então. No entanto, se você estiver escrevendo sua própria coleção, é muito difícil aprender. E sua utilidade diminui quando o tipo de sua coleção é determinado em tempo de execução - quando a coleção possui um método que informa a classe de suas entradas. Nesse momento, os genéricos não funcionam e seu IDE produzirá centenas de avisos sem sentido. 99,99% da programação Java não envolve isso. Mas eu apenas tinha um monte de problemas com isso quando eu escrevi acima.
RalphChapin

Sendo desenvolvedor Java e C #, estou me perguntando por que você prefere coleções Java?
Ivaylo Slavov

Fine. But without generics this error would throw a ClassCastException really quick at run time with little time wasted.Isso realmente depende de quanto tempo de execução ocorre entre a inicialização do programa e a entrada na linha de código em questão. Se um usuário informa o erro e ele te leva vários minutos (ou, pior cenário, horas ou mesmo dias) para reproduzir, que a verificação em tempo de compilação começa a olhar melhor e melhor ...
Mason Wheeler
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.