Quais são os benefícios da eliminação de tipos do Java?

98

Eu li um tweet hoje que dizia:

É engraçado quando os usuários Java reclamam do apagamento de tipo, que é a única coisa que o Java acertou, enquanto ignoram todas as coisas erradas.

Portanto, minha pergunta é:

Existem benefícios da eliminação de tipo do Java? Quais são os benefícios técnicos ou de estilo de programação que ele (possivelmente) oferece, além da preferência de implementações JVM para compatibilidade com versões anteriores e desempenho de tempo de execução?

Verti
fonte
6
Se você não deseja que sua pergunta seja encerrada como "baseada em opinião", pergunte de forma diferente. (Como "quais são as vantagens técnicas do apagamento de tipo"). Melhor ainda, pergunte ao tweeter o que ele realmente está dizendo.
Stephen C
3
Remova o "por que isso é uma coisa boa". A "coisa boa" é uma caracterização patentemente subjetiva e (obviamente) levanta a questão de que o apagamento de tipo é uma coisa boa ... o que é controverso.
Stephen C
3
Que Deus nos ajude se este site vai degenerar em perguntas sobre tudo o que já foi postado no Twitter. Cite pelo menos uma fonte confiável para sua pergunta.
Marquês de Lorne
21
Por que a pergunta precisa ser de "fonte confiável"? Isso é bobagem.
vertti
4
Bem, a maioria dos problemas aqui não vem de NENHUMA fonte, exceto o próprio problema dos questionadores com software / linguagem / outros enfeites específicos, e felizmente há muitos profissionais que "perdem" seu tempo ajudando essas pessoas.
vertti

Respostas:

90

Apagamento de tipo é bom

Vamos nos ater aos fatos

Muitas das respostas até agora estão excessivamente preocupadas com o usuário do Twitter. É útil manter o foco nas mensagens e não no mensageiro. Há uma mensagem bastante consistente mesmo apenas com os trechos mencionados até agora:

É engraçado quando os usuários Java reclamam do apagamento de tipo, que é a única coisa que o Java acertou, enquanto ignoram todas as coisas erradas.

Eu obtenho enormes benefícios (por exemplo, parametricidade) e custo nulo (o custo alegado é um limite da imaginação).

new T é um programa quebrado. É isomórfico à afirmação "todas as proposições são verdadeiras". Eu não sou grande nisso.

Uma meta: programas razoáveis

Esses tweets refletem uma perspectiva que não está interessada em saber se podemos fazer a máquina fazer algo , mas mais se podemos raciocinar que a máquina fará algo que realmente queremos. O bom raciocínio é uma prova. As provas podem ser especificadas em notação formal ou algo menos formal. Independentemente da linguagem de especificação, eles devem ser claros e rigorosos. As especificações informais não são impossíveis de estruturar corretamente, mas costumam apresentar falhas na programação prática. Acabamos com remediações como testes automatizados e exploratórios para compensar os problemas que temos com o raciocínio informal. Isso não quer dizer que o teste seja intrinsecamente uma má ideia, mas o usuário do Twitter citado está sugerindo que existe uma maneira muito melhor.

Portanto, nosso objetivo é ter programas corretos sobre os quais possamos raciocinar de forma clara e rigorosa de uma forma que corresponda a como a máquina realmente executará o programa. Este, porém, não é o único objetivo. Também queremos que nossa lógica tenha um certo grau de expressividade. Por exemplo, há muito que podemos expressar com a lógica proposicional. É bom ter quantificação universal (∀) e existencial (∃) de algo como lógica de primeira ordem.

Usando sistemas de tipo para raciocinar

Esses objetivos podem ser muito bem tratados por sistemas de tipos. Isso é especialmente claro por causa da correspondência Curry-Howard . Essa correspondência é freqüentemente expressa com a seguinte analogia: os tipos estão para os programas assim como os teoremas estão para as provas.

Essa correspondência é um tanto profunda. Podemos pegar expressões lógicas e traduzi-las por meio da correspondência de tipos. Então, se temos um programa com a mesma assinatura de tipo que compila, provamos que a expressão lógica é universalmente verdadeira (uma tautologia). Isso ocorre porque a correspondência é bidirecional. A transformação entre o tipo / programa e os mundos do teorema / prova é mecânica e pode, em muitos casos, ser automatizada.

Curry-Howard joga bem com o que gostaríamos de fazer com as especificações de um programa.

Os sistemas de tipos são úteis em Java?

Mesmo com uma compreensão de Curry-Howard, algumas pessoas acham fácil descartar o valor de um sistema de tipos, quando

  1. é extremamente difícil de trabalhar
  2. corresponde (por meio de Curry-Howard) a uma lógica com expressividade limitada
  3. está quebrado (o que leva à caracterização dos sistemas como "fracos" ou "fortes").

Com relação ao primeiro ponto, talvez os IDEs tornem o sistema de tipos Java fácil de trabalhar (isso é altamente subjetivo).

Em relação ao segundo ponto, Java passa a corresponder quase a uma lógica de primeira ordem. Os genéricos fornecem o equivalente ao sistema de tipos da quantificação universal. Infelizmente, os curingas nos fornecem apenas uma pequena fração da quantificação existencial. Mas a quantificação universal é um bom começo. É bom poder dizer que funciona List<A>universalmente para todas as listas possíveis porque A é completamente irrestrito. Isso leva ao que o usuário do Twitter está falando com respeito à "parametricidade".

Um artigo frequentemente citado sobre parametricidade são os Teoremas de Philip Wadler gratuitamente! . O que é interessante sobre este artigo é que apenas com a assinatura de tipo sozinha, podemos provar alguns invariantes muito interessantes. Se tivéssemos que escrever testes automatizados para essas invariantes, estaríamos perdendo muito nosso tempo. Por exemplo, para List<A>, da assinatura de tipo sozinha paraflatten

<A> List<A> flatten(List<List<A>> nestedLists);

podemos raciocinar que

flatten(nestedList.map(l -> l.map(any_function)))
     flatten(nestList).map(any_function)

Este é um exemplo simples, e você provavelmente pode raciocinar sobre isso informalmente, mas é ainda mais agradável quando obtemos essas provas formalmente de graça no sistema de tipos e verificadas pelo compilador.

Não apagar pode levar a abusos

Do ponto de vista da implementação da linguagem, os genéricos de Java (que correspondem aos tipos universais) jogam fortemente na parametricidade usada para obter provas sobre o que nossos programas fazem. Isso leva ao terceiro problema mencionado. Todos esses ganhos de prova e correção requerem um sistema de tipo de som implementado sem defeitos. Java definitivamente tem alguns recursos de linguagem que nos permitem quebrar nosso raciocínio. Estes incluem, mas não estão limitados a:

  • efeitos colaterais com um sistema externo
  • reflexão

Os genéricos não apagados estão, de muitas maneiras, relacionados à reflexão. Sem exclusão, há informações de tempo de execução que são transportadas com a implementação e que podemos usar para projetar nossos algoritmos. O que isso significa é que, estaticamente, quando raciocinamos sobre programas, não temos o quadro completo. A reflexão ameaça severamente a correção de quaisquer provas sobre as quais raciocinamos estaticamente. Não é por acaso que a reflexão também leva a uma variedade de defeitos complicados.

Então, de que maneiras os genéricos não apagados podem ser "úteis"? Vamos considerar o uso mencionado no tweet:

<T> T broken { return new T(); }

O que acontece se T não tiver um construtor sem arg? Em alguns idiomas, o que você obtém é nulo. Ou talvez você ignore o valor nulo e vá direto para o lançamento de uma exceção (à qual os valores nulos parecem levar de qualquer maneira). Como nossa linguagem é Turing completa, é impossível raciocinar sobre quais chamadas para brokenenvolverão tipos "seguros" com construtores sem arg e quais não. Perdemos a certeza de que nosso programa funciona universalmente.

Apagar significa que raciocinamos (então vamos apagar)

Portanto, se quisermos raciocinar sobre nossos programas, somos fortemente aconselhados a não empregar recursos de linguagem que ameacem fortemente nosso raciocínio. Depois de fazer isso, por que não simplesmente descartar os tipos em tempo de execução? Eles não são necessários. Podemos obter alguma eficiência e simplicidade com a satisfação de que nenhuma conversão falhará ou que métodos podem estar faltando na invocação.

Apagar incentiva o raciocínio.

Sukant Hajra
fonte
7
No tempo que levei para montar essa resposta, algumas outras pessoas responderam com respostas semelhantes. Mas tendo escrito tanto, decidi postar ao invés de descartá-lo.
Sukant Hajra
32
Com respeito, o apagamento de tipo foi uma tentativa meia-boca de trazer um recurso para o Java sem quebrar a compatibilidade com versões anteriores. Não é uma força. Se a principal preocupação era tentar instanciar um objeto sem construtor sem parâmetros, a resposta certa é permitir uma restrição de "tipos com construtores sem parâmetros", não para incapacitar um recurso de linguagem.
Básico
5
Básico, com respeito, você não está fazendo um argumento coeso. Você está meramente afirmando que o apagamento é "incapacitante" com um completo desprezo pelo valor das provas em tempo de compilação como uma ferramenta atraente para raciocinar sobre programas. Além disso, esses benefícios são completamente ortogonais ao caminho tortuoso e à motivação que Java adotou para implementar os genéricos. Além disso, esses benefícios são válidos para idiomas com implementação muito mais esclarecida de eliminação.
Sukant Hajra
44
O argumento apresentado aqui não é contra o apagamento, é contra o reflexo (e verificação de tipo em tempo de execução em geral) - basicamente afirma que o reflexo é ruim, então o fato de o apagamento não funcionar bem com eles é bom. É um ponto válido por si só (embora muitas pessoas discordem); mas não faz muito sentido no contexto, porque Java já tem verificações de reflexão / tempo de execução, e elas não desapareceram e não desaparecerão. Portanto, ter uma construção que não brinca com a parte existente e não obsoleta da linguagem é uma limitação, não importa como você olhe para isso.
Pavel Minaev
10
Sua resposta está simplesmente fora do tópico. Você insere uma explicação prolixa (embora interessante em seus próprios termos) de por que a eliminação de tipo como um conceito abstrato é geralmente útil no projeto de sistemas de tipo, mas o tópico é o benefício de uma característica específica do Java Generics rotulada como "eliminação de tipo ", que se assemelha apenas superficialmente ao conceito que você descreve, falhando completamente em fornecer invariantes interessantes e introduzindo restrições irracionais.
Marko Topolnik
40

Tipos são construções usadas para escrever programas de uma maneira que permite ao compilador verificar a exatidão de um programa. Um tipo é uma proposição sobre um valor - o compilador verifica se essa proposição é verdadeira.

Durante a execução de um programa, não deve haver necessidade de informações de tipo - isso já foi verificado pelo compilador. O compilador deve estar livre para descartar essas informações a fim de realizar otimizações no código - torná-lo mais rápido, gerar um binário menor etc. O apagamento dos parâmetros de tipo facilita isso.

Java interrompe a tipagem estática permitindo que as informações de tipo sejam consultadas em tempo de execução - reflexão, instância de etc. Isso permite que você construa programas que não podem ser verificados estaticamente - eles ignoram o sistema de tipos. Também perde oportunidades de otimização estática.

O fato de que os parâmetros de tipo são apagados evita que algumas instâncias desses programas incorretos sejam construídos; no entanto, programas mais incorretos não seriam permitidos se mais informações de tipo fossem apagadas e os recursos de reflexão e instância de instância fossem removidos.

O apagamento é importante para manter a propriedade de "parametricidade" de um tipo de dados. Digamos que eu tenha um tipo "Lista" parametrizado sobre o tipo de componente T. ou seja, Lista <T>. Esse tipo é uma proposição de que esse tipo de Lista funciona de maneira idêntica para qualquer tipo T. O fato de T ser um parâmetro de tipo abstrato e ilimitado significa que não sabemos nada sobre esse tipo, portanto, somos impedidos de fazer qualquer coisa especial para casos especiais de T.

por exemplo, diga que tenho uma Lista xs = asList ("3"). Eu adiciono um elemento: xs.add ("q"). Acabo com ["3", "q"]. Como isso é paramétrico, posso assumir que List xs = asList (7); xs.add (8) termina com [7,8]. Eu sei pelo tipo que ele não faz uma coisa para String e outra para Int.

Além disso, eu sei que a função List.add não pode inventar valores de T do nada. Eu sei que se minha asList ("3") tiver um "7" adicionado a ela, as únicas respostas possíveis seriam construídas com os valores "3" e "7". Não há possibilidade de um "2" ou "z" ser adicionado à lista porque a função não seria capaz de construí-la. Nenhum desses outros valores seria sensato adicionar, e a parametricidade impede que esses programas incorretos sejam construídos.

Basicamente, o apagamento impede alguns meios de violar a parametricidade, eliminando assim possibilidades de programas incorretos, que é o objetivo da digitação estática.

techtangents
fonte
15

(Embora eu já tenha escrito uma resposta aqui, revisitando esta questão dois anos depois, percebi que existe uma outra maneira completamente diferente de respondê-la, então estou deixando a resposta anterior intacta e adicionando esta.)


É altamente discutível se o processo feito em Java Generics merece o nome de "eliminação de tipo". Uma vez que os tipos genéricos não são apagados, mas substituídos por suas contrapartes brutas, uma escolha melhor parece ser "mutilação de tipo".

A característica quintessencial do apagamento de tipo em seu sentido comumente compreendido é forçar o tempo de execução a permanecer dentro dos limites do sistema de tipo estático, tornando-o "cego" para a estrutura dos dados que acessa. Isso dá força total ao compilador e permite que ele prove teoremas baseados apenas em tipos estáticos. Também ajuda o programador restringindo os graus de liberdade do código, dando mais poder ao raciocínio simples.

O apagamento de tipo do Java não consegue isso - ele paralisa o compilador, como neste exemplo:

void doStuff(List<Integer> collection) { 
}

void doStuff(List<String> collection) // ERROR: a method cannot have 
                   // overloads which only differ in type parameters

(As duas declarações acima se resumem na mesma assinatura de método após o apagamento.)

Por outro lado, o tempo de execução ainda pode inspecionar o tipo de um objeto e raciocinar sobre ele, mas como seu insight sobre o tipo verdadeiro é prejudicado pelo apagamento, as violações de tipo estático são triviais de se obter e difíceis de prevenir.

Para tornar as coisas ainda mais complicadas, as assinaturas de tipo original e apagada coexistem e são consideradas em paralelo durante a compilação. Isso ocorre porque todo o processo não é para remover informações de tipo do tempo de execução, mas sobre colocar um sistema de tipo genérico em um sistema de tipo bruto legado para manter a compatibilidade com versões anteriores. Esta joia é um exemplo clássico:

public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll)

(O redundante extends Objectteve que ser adicionado para preservar a compatibilidade com versões anteriores da assinatura apagada.)

Agora, com isso em mente, vamos revisitar a citação:

É engraçado quando os usuários Java reclamam do apagamento de tipo, que é a única coisa que o Java acertou

O que exatamente o Java acertou? É a própria palavra, independentemente do significado? Para contraste, dê uma olhada no inttipo humilde : nenhuma verificação de tipo em tempo de execução é executada, ou mesmo possível, e a execução é sempre perfeitamente segura para o tipo. Essa é a aparência do apagamento de tipo quando feito da maneira certa: você nem sabe que está lá.

Marko Topolnik
fonte
10

A única coisa que não vejo considerado aqui é que o polimorfismo de tempo de execução OOP é fundamentalmente dependente da reificação de tipos em tempo de execução. Quando uma linguagem cuja espinha dorsal é mantida no lugar por tipos refinados introduz uma extensão principal ao seu sistema de tipos e se baseia na eliminação de tipos, a dissonância cognitiva é o resultado inevitável. Isso é exatamente o que aconteceu com a comunidade Java; é por isso que o apagamento de tipo atraiu tanta controvérsia e, em última análise, por que existem planos para desfazê-lo em uma versão futura do Java . Encontrar algo engraçado nessa reclamação dos usuários de Java denuncia um mal-entendido honesto do espírito de Java ou uma piada conscientemente depreciativa.

A afirmação "exclusão é a única coisa que Java acertou" implica a afirmação de que "todas as linguagens baseadas em despacho dinâmico contra o tipo de argumento de função em tempo de execução são fundamentalmente falhos". Embora certamente uma afirmação legítima por si só, e que pode até ser considerada uma crítica válida de todas as linguagens OOP, incluindo Java, não pode se alojar como um ponto central a partir do qual avaliar e criticar recursos dentro do contexto de Java , onde o polimorfismo do tempo de execução é axiomático.

Em resumo, embora se possa afirmar com validade "a eliminação de tipo é o caminho a percorrer no design de linguagem", as posições que suportam a eliminação de tipo dentro de Java são deslocadas simplesmente porque é tarde demais para isso e já tinha sido até mesmo no momento histórico quando Oak foi abraçado pela Sun e renomeado para Java.



Quanto ao fato de a própria tipagem estática ser a direção apropriada no projeto de linguagens de programação, isso se encaixa em um contexto filosófico muito mais amplo do que pensamos constituir a atividade de programação . Uma escola de pensamento, claramente derivada da tradição clássica da matemática, vê os programas como instâncias de um conceito matemático ou outro (proposições, funções, etc.), mas há uma classe totalmente diferente de abordagens, que vêem a programação como uma forma de fale com a máquina e explique o que queremos dela. Nessa visão, o programa é uma entidade dinâmica e de crescimento orgânico, um oposto dramático do aedifício cuidadosamente erguido de um programa estaticamente tipado.

Pareceria natural considerar as linguagens dinâmicas um passo nessa direção: a consistência do programa surge de baixo para cima, sem constrangimentos a priori que a imponham de cima para baixo. Esse paradigma pode ser visto como um passo em direção à modelagem do processo pelo qual nós, humanos, nos tornamos o que somos por meio do desenvolvimento e do aprendizado.

Marko Topolnik
fonte
Obrigado por isso. É o tipo exato de compreensão mais profunda das filosofias envolvidas que eu esperava ver nesta questão.
Daniel Langdon
O despacho dinâmico não tem nenhuma dependência de tipos reificados. Freqüentemente, depende do que a literatura de programação chama de "tags", mas mesmo assim, não precisa usar tags. Se você criar uma interface (para um objeto) e instâncias anônimas dessa interface, estará realizando despacho dinâmico sem a necessidade de tipos reificados.
Sukant Hajra
@sukanthajra Isso é só confusão. A tag é a informação de tempo de execução necessária para resolver o tipo de comportamento que a instância exibe. Está fora do alcance do sistema de tipos estáticos e essa é a essência do conceito em discussão.
Marko Topolnik de
Existe um tipo muito estático projetado exatamente para esse raciocínio, denominado "tipo de soma". É também chamado de "tipo variante" (no excelente livro de Benjamin Pierce Tipos e Linguagens de Programação). Então, isso não é nenhuma divisão de cabelo. Além disso, você pode usar um catamorfismo / dobra / visitante para uma abordagem completamente sem etiqueta.
Sukant Hajra
Obrigado pela aula teórica, mas não vejo a relevância. Nosso tópico é o sistema de tipos do Java.
Marko Topolnik de
9

Uma postagem subsequente do mesmo usuário na mesma conversa:

new T é um programa quebrado. É isomórfico à afirmação "todas as proposições são verdadeiras". Eu não sou grande nisso.

(Isso foi em resposta a uma declaração de outro usuário, a saber, que "parece que em algumas situações 'novo T' seria melhor", a ideia de que new T()é impossível devido ao apagamento de tipo. (Isso é discutível - mesmo se Testivesse disponível em runtime, pode ser uma classe ou interface abstrata, ou pode ser Void, ou pode não ter um construtor no-arg, ou seu construtor no-arg pode ser privado (por exemplo, porque é suposto ser uma classe singleton), ou seu O construtor no-arg poderia especificar uma exceção verificada que o método genérico não detecta ou especifica - mas essa era a premissa. Independentemente disso, é verdade que, sem a eliminação, você poderia pelo menos escrever T.class.newInstance(), o que lida com esses problemas.))

Essa visão, de que os tipos são isomórficos às proposições, sugere que o usuário tem experiência na teoria formal dos tipos. (S) ele muito provavelmente não gosta de "tipos dinâmicos" ou "tipos de tempo de execução" e prefere um Java sem downcasts instanceofe reflexão e assim por diante. (Pense em uma linguagem como Standard ML, que tem um sistema de tipos muito rico (estático) e cuja semântica dinâmica não depende de nenhuma informação de tipo.

Vale a pena ter em mente, a propósito, que o usuário está trollando: enquanto (s) ele provavelmente prefere sinceramente linguagens digitadas (estaticamente), ele não está sinceramente tentando persuadir os outros dessa visão. Em vez disso, o objetivo principal do tweet original era zombar daqueles que discordam e, depois que alguns desses discordantes entraram na conversa, o usuário postou tweets de acompanhamento, como "a razão de java ter apagamento de tipo é que Wadler e outros sabem o que eles estão fazendo, ao contrário dos usuários de java ". Infelizmente, isso torna difícil descobrir o que ele está realmente pensando; mas, felizmente, provavelmente também significa que não é muito importante fazer isso. Pessoas com profundidade real em suas opiniões geralmente não recorrem a trolls que são totalmente livres de conteúdo.

Ruakh
fonte
2
Ele não compila em Java porque tem apagamento de tipo
Mark Rotteveel
1
@MarkRotteveel: Obrigado; Eu adicionei um parágrafo para explicar (e comentar) isso.
ruakh
1
A julgar pela mistura de votos positivos e negativos, esta é a resposta mais controversa que já postei. Estou estranhamente orgulhoso.
ruakh
6

Uma coisa boa é que não houve necessidade de alterar a JVM quando os genéricos foram introduzidos. Java implementa genéricos apenas no nível do compilador.

Evgeniy Dorofeev
fonte
1
Mudanças na JVM comparadas a quê? Os modelos também não teriam exigido mudanças na JVM.
Marquês de Lorne
5

A razão pela qual o apagamento de tipo é uma coisa boa é que as coisas que ela torna impossível são prejudiciais. Impedir a inspeção de argumentos de tipo em tempo de execução facilita a compreensão e raciocínio sobre os programas.

Uma observação que achei um tanto contra-intuitiva é que, quando as assinaturas de função são mais genéricas, elas se tornam mais fáceis de entender. Isso ocorre porque o número de implementações possíveis é reduzido. Considere um método com esta assinatura, que de alguma forma sabemos que não tem efeitos colaterais:

public List<Integer> XXX(final List<Integer> l);

Quais são as possíveis implementações desta função? Muitos. Você pode dizer muito pouco sobre o que essa função faz. Pode estar revertendo a lista de entrada. Pode ser emparelhar ints, somar e retornar uma lista com metade do tamanho. Existem muitas outras possibilidades que poderiam ser imaginadas. Agora considere:

public <T> List<T> XXX(final List<T> l);

Quantas implementações desta função existem? Uma vez que a implementação não pode saber o tipo dos elementos, um grande número de implementações pode agora ser excluído: os elementos não podem ser combinados, ou adicionados à lista ou filtrados, et al. Estamos limitados a coisas como: identidade (sem alteração na lista), descartar elementos ou inverter a lista. Esta função é mais fácil de raciocinar com base apenas em sua assinatura.

Exceto ... em Java você sempre pode enganar o sistema de tipos. Como a implementação desse método genérico pode usar coisas como instanceofverificações e / ou conversões para tipos arbitrários, nosso raciocínio baseado na assinatura de tipo pode ser facilmente tornado inútil. A função pode inspecionar o tipo dos elementos e fazer uma série de coisas com base no resultado. Se esses hacks de tempo de execução forem permitidos, as assinaturas de método parametrizadas se tornarão muito menos úteis para nós.

Se o Java não tivesse apagamento de tipo (ou seja, os argumentos de tipo fossem reificados em tempo de execução), isso simplesmente permitiria mais travessuras desse tipo que prejudicam o raciocínio. No exemplo acima, a implementação só pode violar as expectativas definidas pela assinatura de tipo se a lista tiver pelo menos um elemento; mas se Tfosse reificado, poderia fazê-lo mesmo se a lista estivesse vazia. Os tipos reificados apenas aumentariam as (já muitas) possibilidades de impedir nossa compreensão do código.

A eliminação do tipo torna a linguagem menos "poderosa". Mas algumas formas de "poder" são realmente prejudiciais.

Lachlan
fonte
1
Parece que você está argumentando que os genéricos são muito complexos para os desenvolvedores de Java "entenderem e raciocinarem" a respeito, ou que não se pode confiar que eles não darão um tiro no próprio pé se tiverem a chance. O último parece estar em sincronia com o espírito do Java (sem tipos não assinados, etc), mas parece um pouco condescendente para os desenvolvedores
Básico
1
@Basic Devo ter me expressado muito mal, porque não foi isso que eu quis dizer. O problema não é que os genéricos sejam complexos, mas coisas como a projeção e instanceofimpedem nossa capacidade de raciocinar sobre o que o código faz com base em tipos. Se o Java reificasse os argumentos de tipo, só pioraria o problema. Apagar tipos em tempo de execução tem o efeito de tornar o sistema de tipos mais útil.
Lachlan
@lachlan, fez todo o sentido para mim, e não acho que uma leitura normal dele seria um insulto aos desenvolvedores de Java.
Rob Grant
3

Esta não é uma resposta direta (OP perguntou "quais são os benefícios", estou respondendo "quais são os contras")

Comparado ao sistema de tipo C #, o apagamento de tipo Java é uma dor real para dois raesons

Você não pode implementar uma interface duas vezes

Em C #, você pode implementar ambos IEnumerable<T1>e IEnumerable<T2>com segurança, especialmente se os dois tipos não compartilham um ancestral comum (ou seja, seu ancestral é Object ).

Exemplo prático: no Spring Framework, você não pode implementar ApplicationListener<? extends ApplicationEvent>várias vezes. Se você precisa de comportamentos diferentes com base em, Tvocê precisa testarinstanceof

Você não pode fazer novo T ()

(e você precisa de uma referência à classe para fazer isso)

Como outros comentaram, fazer o equivalente de new T()só pode ser feito por meio de reflexão, apenas invocando uma instância de Class<T>, certificando-se dos parâmetros exigidos pelo construtor. C # permite que você faça new T() apenas se você restringir Tao construtor sem parâmetros. Se Tnão respeitar essa restrição, será gerado um erro de compilação .

Em Java, você frequentemente será forçado a escrever métodos semelhantes aos seguintes

public <T> T create(....params, Class<T> classOfT)
{

    ... whatever you do
    ... you will end up
    T = classOfT.newInstance();


    ... or more advanced reflection
    Constructor<T> parameterizedConstructorThatYouKnowAbout = classOfT.getConstructor(...,...);
}

As desvantagens do código acima são:

  • Class.newInstance só funciona com um construtor sem parâmetros. Se nenhum estiver disponível, ReflectiveOperationExceptioné lançado em tempo de execução
  • O construtor refletido não destaca problemas em tempo de compilação como o acima. Se você refatorar, de seus argumentos de troca, você saberá apenas em tempo de execução

Se eu fosse o autor do C #, teria introduzido a capacidade de especificar uma ou mais restrições de construtor que são fáceis de verificar em tempo de compilação (portanto, posso exigir, por exemplo, um construtor com string,stringparâmetros). Mas o último é especulação

usr-local-ΕΨΗΕΛΩΝ
fonte
2

Um ponto adicional que nenhuma das outras respostas parece ter considerado: se você realmente precisa de genéricos com tipagem em tempo de execução , você mesmo pode implementá-lo desta forma:

public class GenericClass<T>
{
     private Class<T> targetClass;
     public GenericClass(Class<T> targetClass)
     {
          this.targetClass = targetClass;
     }

Esta classe é então capaz de fazer todas as coisas que seriam alcançáveis ​​por padrão se o Java não usasse erasure: ela pode alocar novos Ts (assumindo que Ttenha um construtor que corresponda ao padrão que espera usar), ou matrizes de Ts, pode teste dinamicamente em tempo de execução se um determinado objeto é um Te mude o comportamento dependendo disso, e assim por diante.

Por exemplo:

     public T newT () { 
         try {
             return targetClass.newInstance(); 
         } catch(/* I forget which exceptions can be thrown here */) { ... }
     }

     private T value;
     /** @throws ClassCastException if object is not a T */
     public void setValueFromObject (Object object) {
         value = targetClass.cast(object);
     }
}
Jules
fonte
Alguém se importa em explicar o motivo do downvote? Isso é, até onde posso ver, uma explicação perfeitamente boa de por que a suposta desvantagem do sistema de eliminação de tipo do Java não é realmente uma limitação real. Então qual é o problema?
Jules
Estou votando positivamente. Não que eu suporte a verificação de tipo em tempo de execução, mas esse truque pode ser útil nas minas de sal.
techtangents
3
Java é uma linguagem das minas de sal, o que torna isso uma necessidade absoluta dada a falta de informações sobre o tipo de tempo de execução. Esperançosamente, o apagamento de tipo será desfeito em uma versão futura do Java, tornando os genéricos um recurso muito mais útil. Atualmente, o consenso é que a relação empuxo / peso está abaixo de 1.
Marko Topolnik
Bem, claro ... Contanto que seu TargetType não use genéricos. Mas isso parece bastante limitante.
Básico
Também funciona com tipos genéricos. A única restrição é que, se você deseja criar uma instância, você precisa de um construtor com os tipos de argumento corretos, mas todas as outras linguagens que conheço têm a mesma restrição, portanto, não vejo a limitação.
Jules
0

evita o inchaço de código semelhante ao c ++ porque o mesmo código é usado para vários tipos; no entanto, o apagamento de tipo requer envio virtual, enquanto a abordagem c ++ - inchaço de código pode fazer genéricos não despachados virtualmente

necromante
fonte
3
A maioria desses despachos virtuais são convertidos em estáticos em tempo de execução, portanto, não é tão ruim quanto pode parecer.
Piotr Kołaczkowski
1
bem verdade, a disponibilidade de um compilador em tempo de execução permite que o java faça muitas coisas legais (e obtenha alto desempenho) em relação ao c ++.
necromante
uau, deixe-me escrever em algum lugar java atinge alto desempenho em relação ao cpp ..
jungle_mole
1
@jungle_mole sim, obrigado. poucas pessoas percebem o benefício de ter um compilador disponível em tempo de execução para compilar o código novamente após a criação de perfil. (ou que a coleta de lixo é big-oh (não lixo) ao invés da crença ingênua e incorreta de que a coleta de lixo é big-oh (lixo), (ou que embora a coleta de lixo seja um pequeno esforço, ela compensa permitindo custo zero atribuição)). É um mundo triste onde as pessoas adotam cegamente o que sua comunidade acredita e param de raciocinar com base nos primeiros princípios.
necromante de
0

A maioria das respostas está mais preocupada com a filosofia de programação do que com os detalhes técnicos reais.

E embora essa pergunta tenha mais de 5 anos, a pergunta ainda persiste: Por que a eliminação de tipo é desejável do ponto de vista técnico? No final, a resposta é bastante simples (em um nível superior): https://en.wikipedia.org/wiki/Type_erasure

Os modelos C ++ não existem em tempo de execução. O compilador emite uma versão totalmente otimizada para cada invocação, o que significa que a execução não depende das informações de tipo. Mas como um JIT lida com diferentes versões da mesma função? Não seria melhor ter apenas uma função? Não gostaria que o JIT tivesse que otimizar todas as diferentes versões dele. Bem, mas e quanto à segurança de tipo? Acho que isso tem que sair pela janela.

Mas espere um segundo: como o .NET faz isso? Reflexão! Dessa forma, eles só precisam otimizar uma função e também obter informações sobre o tipo de tempo de execução. E é por isso que os genéricos .NET costumavam ser mais lentos (embora tenham ficado muito melhores). Não estou argumentando que isso não seja conveniente! Mas é caro e não deve ser usado quando não for absolutamente necessário (não é considerado caro em linguagens tipadas dinamicamente porque o compilador / interpretador depende da reflexão de qualquer maneira).

Desta forma, a programação genérica com eliminação de tipo é quase zero (algumas verificações / conversões de tempo de execução ainda são necessárias): https://docs.oracle.com/javase/tutorial/java/generics/erasure.html

Marvin H.
fonte