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 constructors
argumentos que aceitam esses argumentos, porque então você não pode simplesmente mudar o nome de um dos constructors
.
List
e não sei como lidar com isso.Respostas:
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:
Você estende minha classe, assim:
Após a introdução dos genéricos, decidi atualizar minha biblioteca.
Você não está pronto para fazer atualizações; portanto, deixe sua
Overrider
turma em paz. Para substituir otoList()
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: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.
fonte
javac
.List<?>
e algunsenum
que 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.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 queadd(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.
fonte
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 )
Ambos os métodos têm a mesma assinatura sem os parâmetros de tipo, daí o erro.
fonte
O problema é esse
Set<Integer>
e,Set<String>
na verdade, é tratado como umSet
da 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 entreSet<String>
eSet<Integer>
.fonte
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.(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.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.
fonte
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.
fonte
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.Eu me deparei com isso quando tentei escrever algo como:
Continuable<T> callAsync(Callable<T> code) {....}
eContinuable<Continuable<T>> callAsync(Callable<Continuable<T>> veryAsyncCode) {...}
Eles se tornam para compilador as 2 definições deContinuable<> 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
Ou com alguns nomes mais descritivos, auto-documentando casos de oyu, como
addNames
eaddIndexes
ou não.fonte