O método tem o mesmo apagamento que outro método no tipo


381

Por que não é legal ter os dois métodos a seguir na mesma classe?

class Test{
   void add(Set<Integer> ii){}
   void add(Set<String> ss){}
}

Eu recebo o compilation error

O método add (Set) tem a mesma eliminação de apagamento (Set) de outro método no tipo Test.

enquanto eu posso contornar isso, fiquei me perguntando por que o javac não gosta disso.

Percebo que, em muitos casos, a lógica desses dois métodos seria muito semelhante e poderia ser substituída por um único

public void add(Set<?> set){}

método, mas esse nem sempre é o caso.

Isso é muito irritante se você quiser ter dois constructorsargumentos que aceitam esses argumentos, porque então você não pode simplesmente mudar o nome de um dos constructors.


11
e se você ficar sem estruturas de dados e ainda precisar de mais versões?
Omry Yadan

11
Você pode criar classes personalizadas que herdam das versões base.
willlma

11
OP, você encontrou alguma solução para o problema do construtor? Preciso aceitar dois tipos Liste não sei como lidar com isso.
Tomáš Zato - Restabelece Monica

9
Ao trabalhar com Java, eu realmente sinto falta C # ...
Svish

11
@ TomášZato, resolvi isso adicionando parâmetros simulados ao construtor: Boolean noopSignatureOverload.
Nthalk 03/09/19

Respostas:


357

Esta regra tem como objetivo evitar conflitos no código legado que ainda usa tipos brutos.

Aqui está uma ilustração de por que isso não foi permitido, extraído do JLS. Suponha que, antes de os genéricos serem introduzidos no Java, eu escrevi um código como este:

class CollectionConverter {
  List toList(Collection c) {...}
}

Você estende minha classe, assim:

class Overrider extends CollectionConverter{
  List toList(Collection c) {...}
}

Após a introdução dos genéricos, decidi atualizar minha biblioteca.

class CollectionConverter {
  <T> List<T> toList(Collection<T> c) {...}
}

Você não está pronto para fazer atualizações; portanto, deixe sua Overriderturma em paz. Para substituir o toList()método corretamente , os designers de linguagem decidiram que um tipo bruto era "equivalente a substituição" para qualquer tipo gerado. Isso significa que, embora sua assinatura de método não seja mais formalmente igual à assinatura da minha superclasse, seu método ainda substitui.

Agora, o tempo passa e você decide que está pronto para atualizar sua turma. Mas você estraga um pouco e, em vez de editar o toList()método bruto existente, adiciona um novo método como este:

class Overrider extends CollectionConverter {
  @Override
  List toList(Collection c) {...}
  @Override
  <T> List<T> toList(Collection<T> c) {...}
}

Devido à equivalência de substituição dos tipos brutos, os dois métodos estão em uma forma válida para substituir o toList(Collection<T>)método. Mas é claro que o compilador precisa resolver um único método. Para eliminar essa ambiguidade, as classes não têm permissão para ter vários métodos que são equivalentes a substituição - ou seja, vários métodos com os mesmos tipos de parâmetro após a exclusão.

A chave é que essa é uma regra de idioma projetada para manter a compatibilidade com o código antigo usando tipos brutos. Não é uma limitação exigida pelo apagamento dos parâmetros de tipo; como a resolução do método ocorre no tempo de compilação, a adição de tipos genéricos ao identificador do método seria suficiente.


3
Ótima resposta e exemplo! Não tenho certeza, no entanto, se entendi completamente sua última frase ("Como a resolução do método ocorre no tempo de compilação, antes do apagamento, a reificação de tipo não é necessária para fazer esse trabalho"). Você poderia elaborar um pouco?
perfil completo de Jonas Eicher

2
Faz sentido. Passei um tempo pensando na reificação de tipos nos métodos de modelo, mas sim: o compilador garante que o método certo seja selecionado antes da exclusão do tipo. Bonita. Se não estivesse corrompido pelos problemas de compatibilidade de código herdado.
Jonas Eicher

11
@daveloyall Não, não conheço essa opção javac.
Erickson

13
Não é a primeira vez que encontro um erro do Java, que não é nenhum erro e poderia ser compilado se apenas os autores do Java usassem avisos como todos os outros. Só eles pensam que sabem tudo melhor.
Tomáš Zato - Restabelecer Monica

11
@ TomášZato Não, não conheço nenhuma solução limpa para isso. Se você quiser algo sujo, pode passar o List<?>e alguns enumque você define para indicar o tipo. A lógica específica do tipo pode realmente estar em um método no enum. Como alternativa, convém criar duas classes diferentes, em vez de uma classe com dois construtores diferentes. A lógica comum seria uma superclasse ou um objeto auxiliar ao qual os dois tipos delegam.
Erickson

118

Os genéricos Java usam apagamento de tipo. O bit entre colchetes angulares ( <Integer>e <String>) é removido, então você acaba com dois métodos com uma assinatura idêntica (a que add(Set)você vê no erro). Isso não é permitido porque o tempo de execução não saberia qual usar para cada caso.

Se o Java tiver genéricos reificados, você poderá fazer isso, mas isso provavelmente é improvável agora.


24
Sinto muito, mas sua resposta (e as outras respostas) não explicam por que há um erro aqui. A resolução de sobrecarga é feita no tempo de compilação e o compilador certamente possui as informações de tipo necessárias para decidir qual método vincular por endereço ou por qualquer método que seja referenciado no bytecode, que eu acredito que não seja assinatura. Eu até acho que alguns compiladores permitirão que isso seja compilado.
Stilgar

5
@ Stilgar, o que impede que o método seja chamado ou inspecionado por meio de reflexão? A lista de métodos retornados por Class.getMethods () teria dois métodos idênticos, o que não faria sentido.
Adrian Mouat

5
As informações de reflexão podem / devem conter os metadados necessários para trabalhar com genéricos. Caso contrário, como o compilador Java sabe sobre métodos genéricos quando você importa uma biblioteca já compilada?
Stilgar

4
Em seguida, o método getMethod precisa ser corrigido. Por exemplo, introduza uma sobrecarga que especifique sobrecarga genérica e faça com que o método original retorne apenas a versão não genérica, não retornando nenhum método anotado como genérico. Obviamente, isso deveria ter sido feito na versão 1.5. Se o fizerem agora, eles quebrarão a compatibilidade com versões anteriores do método. Eu mantenho minha afirmação de que o apagamento de tipo não dita esse comportamento. É a implementação que não obteve trabalho suficiente provavelmente devido a recursos limitados.
Stilgar

11
Essa não é uma resposta precisa, mas resume rapidamente o problema em uma ficção útil: as assinaturas do método são muito semelhantes, o compilador pode não perceber a diferença e você terá "problemas de compilação não resolvidos".
WORC

46

Isso ocorre porque o Java Generics é implementado com o Type Erasure .

Seus métodos seriam traduzidos, em tempo de compilação, para algo como:

A resolução do método ocorre no tempo de compilação e não considera os parâmetros de tipo. ( veja a resposta de erickson )

void add(Set ii);
void add(Set ss);

Ambos os métodos têm a mesma assinatura sem os parâmetros de tipo, daí o erro.


21

O problema é esse Set<Integer>e, Set<String>na verdade, é tratado como um Setda JVM. Selecionar um tipo para o Set (String ou Inteiro no seu caso) é apenas o açúcar sintático usado pelo compilador. A JVM não pode distinguir entre Set<String>e Set<Integer>.


7
É verdade que o tempo de execução da JVM não possui informações para distinguir cada uma Set, mas como a resolução do método ocorre no tempo de compilação, quando as informações necessárias estão disponíveis, isso não é relevante. O problema é que permitir essas sobrecargas entraria em conflito com a permissão para tipos brutos; portanto, eles foram tornados ilegais na sintaxe Java.
Erickson

@erickson Mesmo quando o compilador sabe qual método chamar, ele não pode, como no bytecode, ambos parecem exatamente iguais. Você precisaria alterar como uma chamada de método é especificada, pois (Ljava/util/Collection;)Ljava/util/List;não funciona. Você pode usar (Ljava/util/Collection<String>;)Ljava/util/List<String>;, mas essa é uma alteração incompatível e você terá problemas insolúveis em locais onde tudo o que tem é um tipo apagado. Você provavelmente teria que eliminar completamente o apagamento, mas isso é bastante complicado.
Maaartinus 18/09/19

@maaartinus Sim, concordo que você precisaria alterar o especificador do método. Estou tentando resolver alguns dos problemas insolúveis que os levaram a desistir dessa tentativa.
22618

7

Defina um único método sem o tipo void add(Set ii){}

Você pode mencionar o tipo ao chamar o método com base em sua escolha. Funcionará para qualquer tipo de conjunto.


3

Pode ser possível que o compilador traduza Set (Integer) para Set (Object) no código java byte. Se for esse o caso, Set (Inteiro) seria usado apenas na fase de compilação para verificação de sintaxe.


5
Tecnicamente, é apenas o tipo bruto Set. Os genéricos não existem no código de bytes, são açúcar sintático para conversão e fornecem segurança do tipo em tempo de compilação.
Andrzej Doyle

1

Eu me deparei com isso quando tentei escrever algo como: Continuable<T> callAsync(Callable<T> code) {....} e Continuable<Continuable<T>> callAsync(Callable<Continuable<T>> veryAsyncCode) {...} Eles se tornam para compilador as 2 definições de Continuable<> callAsync(Callable<> veryAsyncCode) {...}

O apagamento de tipo significa literalmente apagar informações de argumentos de tipo de genéricos. Isso é MUITO irritante, mas é uma limitação que ficará com o Java por enquanto. Para construtores, não se pode fazer muito, 2 novas subclasses especializadas com parâmetros diferentes no construtor, por exemplo. Ou então use métodos de inicialização ... (construtores virtuais?) Com nomes diferentes ...

renomear métodos de operação semelhantes ajudaria, como

class Test{
   void addIntegers(Set<Integer> ii){}
   void addStrings(Set<String> ss){}
}

Ou com alguns nomes mais descritivos, auto-documentando casos de oyu, como addNamese addIndexesou não.

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.