Genéricos e apagamento de tipo

10

Os genéricos em Java são implementados usando o apagamento de tipo. O JLS diz que a inspiração foi a compatibilidade com versões anteriores. Onde, por outro lado, os genéricos C # são reificáveis.

Teoricamente, quais são as vantagens e desvantagens de ter os genéricos como "apagamento" ou "reifiabilidade"?

O Java está faltando em alguma coisa?

Jatin
fonte

Respostas:

8

Bem, a motivação (compatibilidade com versões anteriores) é uma vantagem e uma desvantagem. É uma desvantagem, porque todos nós preferimos ter tipos reificáveis, mas o preço a pagar foi alto. Considere as opções de design em C #. Eles têm tipos reificáveis, mas agora eles têm APIs duplicadas. Então, imagine uma API Java em que também tivemos APIs duplicadas para cada classe parametrizada. Agora, imagine-se portando milhares de linhas de código das classes herdadas para as novas classes genéricas. Agora, quem não consideraria as APIs duplicadas uma desvantagem? Mas ei, eles têm tipos reificáveis!

Portanto, a principal motivação foi "evolução, não revolução". E logicamente, toda decisão tem trocas.

Além das outras desvantagens mencionadas, também podemos acrescentar o fato de que o apagamento de tipo pode ser difícil de raciocinar no momento da compilação, pois não é evidente que determinados tipos serão removidos e isso leva a erros muito estranhos e difíceis de encontrar.

A existência de métodos de ponte ( métodos compilados gerados sintaticamente para manter a compatibilidade binária) também pode ser vista como desvantagem. E essas podem ser precisamente uma das razões dos erros que mencionei no parágrafo anterior.

As principais desvantagens decorrem do fato já óbvio de que existe uma única classe e não várias para tipos genéricos. Como outro exemplo, considere que a sobrecarga de um método com a mesma classe genérica falha em Java:

public void doSomething(List<One>);
public void doSomething(List<Two>);

Algo que pode ser visto como uma desvantagem de tipos reificáveis ​​(pelo menos em C #) é o fato de eles causarem explosão de código . Por exemplo, List<int>é uma classe e a List<double>é outra totalmente diferente, pois é a List<string>e a List<MyType>. Portanto, as classes precisam ser definidas em tempo de execução, causando uma explosão de classes e consumindo recursos valiosos enquanto são gerados.

Quanto ao fato de não ser possível definir a new T()em Java, mencionado em outra resposta, também é interessante considerar que isso não é apenas uma questão de apagamento de tipo. Também requer a existência de um construtor padrão, por isso o C # requer uma "nova restrição" para isso. (Veja Por que o novo T () não é possível em Java , por Alex Buckley).

edalorzo
fonte
3
Acho que estou certo ao dizer que no CLR, para os tipos de referência T s, você recebe apenas uma cópia do Class<T>código para todos os Ts; além de uma cópia adicional para cada tipo de valor T realmente usado.
precisa saber é o seguinte
@AakashM: está correto, existe uma única classe de tipo de referência por genérico, porém cada tipo de valor cria sua própria versão. Isso ocorre devido ao fornecimento de espaço de armazenamento especializado com base no tipo, todas as referências usam o mesmo armazenamento, mas os tipos de valor usam armazenamento diferente por tipo.
precisa saber é o seguinte
6

A desvantagem do apagamento de tipo é que você não sabe em tempo de execução o tipo do genérico. Isso implica que você não pode aplicar reflexão neles e não pode instancia-los em tempo de execução.

Você não pode fazer algo assim em Java:

public class MyClass<T> {
    private T t;

    //more methods    
    public void myMethod() {
        //more code
        if (someCondition) {
            t = new T();//illegal
        } else {
            T[] array = new T[];//illegal
        }            
    }       
}

Existe uma solução alternativa para isso, mas isso requer mais código. A vantagem, como você mencionou, é a compatibilidade com versões anteriores.

m3th0dman
fonte
3

Outra vantagem dos genéricos apagados é que diferentes linguagens que são compiladas na JVM empregam estratégias diferentes para genéricos, por exemplo, site de definição do Scala versus covariância do site de uso do Java. Também os genéricos mais sofisticados do Scala teriam sido mais difíceis de suportar nos tipos reificados do .Net, porque essencialmente o Scala no .Net ignorava o formato de reificação incompatível do C #. Se tivéssemos reificado genéricos na JVM, provavelmente esses genéricos reificados não seriam adequados para os recursos que realmente gostamos no Scala e estaríamos presos a algo abaixo do ideal. Citando o blog de Ola Bini ,

O que tudo isso significa é que, se você deseja adicionar genéricos reificados à JVM, deve ter certeza de que essa implementação pode abranger todas as linguagens estáticas que desejam inovar em sua própria versão de genéricos e todas as linguagens dinâmicas que desejam crie uma boa implementação e um bom recurso de interface com as bibliotecas Java. Como se você adicionar genéricos reificados que não atendam a esses critérios, sufocará a inovação e dificultará muito o uso da JVM como uma VM multilíngue.

Pessoalmente, não considero a necessidade de usar um TypeTagcontexto vinculado no Scala para métodos conflitantes sobrecarregados como uma desvantagem, porque move a sobrecarga e a inflexibilidade da reificação de um global (programa inteiro e todos os idiomas possíveis) para um site de uso por idioma problema que é apenas o caso com menos frequência.

Shelby Moore III
fonte
Outro motivo para favorecer os genéricos apagados é porque as dependências devem ser injetadas de maneira a respeitar a solução do Problema da Expressão. Se você estiver testando casos específicos de tipos ou codificando uma fábrica em sua função genérica, estará fazendo uma extensibilidade incorreta.
Shelby Moore III