Quais são as razões por trás da decisão de não ter um método get totalmente genérico na interface do java.util.Map<K, V>
.
Para esclarecer a questão, a assinatura do método é
V get(Object key)
ao invés de
V get(K key)
e eu estou querendo saber o porquê (a mesma coisa para remove, containsKey, containsValue
).
java
generics
collections
map
WMR
fonte
fonte
Respostas:
Conforme mencionado por outros, a razão pela qual
get()
etc. não é genérica porque a chave da entrada que você está recuperando não precisa ser do mesmo tipo que o objeto para o qual você passaget()
; a especificação do método requer apenas que sejam iguais. Isso segue como oequals()
método recebe um Objeto como parâmetro, não apenas o mesmo tipo que o objeto.Embora possa ser verdade que muitas classes foram
equals()
definidas para que seus objetos possam ser iguais aos objetos de sua própria classe, há muitos lugares em Java onde esse não é o caso. Por exemplo, a especificação paraList.equals()
diz que dois objetos de Lista são iguais se forem Listas e tiverem o mesmo conteúdo, mesmo que sejam implementações diferentes deList
. Então, voltando ao exemplo nesta questão, de acordo com a especificação do método, é possível ter umMap<ArrayList, Something>
e para eu chamarget()
com umLinkedList
argumento as, e ele deve recuperar a chave que é uma lista com o mesmo conteúdo. Isso não seria possível seget()
fosse genérico e restringisse seu tipo de argumento.fonte
V Get(K k)
em c #?m.get(linkedList)
, por que você não definium
o tipo comoMap<List,Something>
? Não consigo pensar em um caso de caso em que fazer chamadasm.get(HappensToBeEqual)
sem alterar oMap
tipo para obter uma interface faça sentido.TreeMap
pode falhar quando você passa objetos do tipo errado para oget
método, mas pode passar ocasionalmente, por exemplo, quando o mapa está vazio. E ainda pior, no caso de um fornecimento,Comparator
ocompare
método (que possui uma assinatura genérica!) Pode ser chamado com argumentos do tipo errado, sem nenhum aviso desmarcado. Este é um comportamento quebrado.Um incrível codificador de Java do Google, Kevin Bourrillion, escreveu exatamente sobre esse problema em um post de blog há um tempo atrás (reconhecidamente no contexto de em
Set
vez deMap
). A frase mais relevante:Não tenho certeza absoluta de que concordo com isso como um princípio - o .NET parece bem exigindo o tipo de chave correto, por exemplo -, mas vale a pena seguir o raciocínio na postagem do blog. (Tendo mencionado o .NET, vale a pena explicar que parte do motivo pelo qual não é um problema no .NET é que há o maior problema no .NET de variação mais limitada ...)
fonte
Integer
e umDouble
nunca sejam iguais, ainda é uma pergunta justa perguntar se aSet<? extends Number>
contém o valornew Integer(5)
.Set<? extends Foo>
. Eu mudei muito frequentemente o tipo de chave de um mapa e fiquei frustrado por o compilador não conseguir encontrar todos os locais onde o código precisava ser atualizado. Realmente não estou convencido de que essa seja a troca correta.O contrato é expresso assim:
(minha ênfase)
e, como tal, uma pesquisa de chave bem-sucedida depende da implementação do método de igualdade pela chave de entrada. Isso não é necessariamente dependente da classe de k.
fonte
hashCode()
. Sem uma implementação adequada de hashCode (), um bem implementadoequals()
é bastante inútil neste caso.get()
não é necessário usar um argumento do tipoObject
para satisfazer o contato. Imagine que o método get estivesse restrito ao tipo de chaveK
- o contrato ainda seria válido. Obviamente, os usos em que o tipo de tempo de compilação não era uma subclasse deK
agora não seriam compilados, mas isso não invalida o contrato, pois os contratos discutem implicitamente o que acontece se o código for compilado.É uma aplicação da Lei de Postel: "seja conservador no que faz, seja liberal no que aceita dos outros".
Verificações de igualdade podem ser realizadas independentemente do tipo; o
equals
método é definido naObject
classe e aceita qualquerObject
como parâmetro. Portanto, faz sentido que a equivalência de chave e operações baseadas na equivalência de chave aceitem qualquerObject
tipo.Quando um mapa retorna valores-chave, ele conserva o máximo possível de informações de tipo, usando o parâmetro type.
fonte
V Get(K k)
em c #?V Get(K k)
em C # porque também faz sentido. A diferença entre as abordagens Java e .NET é realmente apenas quem bloqueia coisas não correspondentes. Em C # é o compilador, em Java é a coleção. Raiva I sobre classes de coleção inconsistentes do .NET vez em quando, masGet()
eRemove()
aceitando apenas um tipo de correspondência certamente impede que você acidentalmente passando um valor errado.contains : K -> boolean
.Acho que esta seção do Tutorial sobre genéricos explica a situação (minha ênfase):
"Você precisa garantir que a API genérica não seja indevidamente restritiva; ela deve continuar a oferecer suporte ao contrato original da API. Considere novamente alguns exemplos de java.util.Collection. A API pré-genérica se parece com:
Uma tentativa ingênua de gerá-lo é:
Embora esse seja certamente um tipo seguro, ele não cumpre o contrato original da API. O método containsAll () funciona com qualquer tipo de coleção recebida. Só terá êxito se a coleção recebida realmente contiver apenas instâncias de E, mas:
fonte
containsAll( Collection< ? extends E > c )
então?containsAll
com umCollection<S>
ondeS
é um supertipo deE
. Isso não seria permitido se fossecontainsAll( Collection< ? extends E > c )
. Além disso, como é explicitamente declarado no exemplo, é legítimo passar uma coleção de um tipo diferente (com o valor de retorno entãofalse
).O motivo é que a contenção é determinada por
equals
ehashCode
quais são os métodos ativadosObject
e ambos assumem umObject
parâmetro. Essa foi uma falha de design anterior nas bibliotecas padrão do Java. Juntamente com as limitações no sistema de tipos do Java, força qualquer coisa que dependa de iguais e hashCode a serem executadasObject
.A única maneira de ter tabelas de hash tipo seguro e igualdade em Java é evitar
Object.equals
eObject.hashCode
e usar um substituto genérico. Java funcional vem com classes de tipo para esse fim:Hash<A>
eEqual<A>
.HashMap<K, V>
É fornecido um wrapper que levaHash<K>
eEqual<K>
em seu construtor. Portanto, essa classeget
econtains
métodos usam um argumento genérico do tipoK
.Exemplo:
fonte
Compatibilidade.
Antes que os genéricos estivessem disponíveis, havia apenas get (Objeto o).
Se eles tivessem alterado esse método para obter (<K> o), isso teria potencialmente forçado a manutenção maciça de código para usuários java apenas para tornar o código de trabalho compilado novamente.
Eles poderiam ter introduzido um método adicional , digamos get_checked (<K> o) e reprovado o antigo método get (), para que houvesse um caminho de transição mais suave. Mas, por alguma razão, isso não foi feito. (A situação em que estamos agora é que você precisa instalar ferramentas como findBugs para verificar a compatibilidade de tipos entre o argumento get () e o tipo de chave declarada <K> do mapa.)
Os argumentos relativos à semântica de .equals () são falsos, eu acho. (Tecnicamente, eles estão corretos, mas ainda acho que são falsos. Nenhum designer em sã consciência jamais tornará o1.equals (o2) verdadeiro se o1 e o2 não tiverem nenhuma superclasse comum.)
fonte
Há mais uma razão pesada, isso não pode ser feito tecnicamente, porque quebra o Map.
Java tem construção genérica polimórfica como
<? extends SomeClass>
. Marcada como referência pode apontar para o tipo assinado<AnySubclassOfSomeClass>
. Mas o genérico polimórfico torna essa referência somente leitura . O compilador permite que você use tipos genéricos apenas como tipo de método retornado (como getters simples), mas bloqueia o uso de métodos em que tipo genérico é argumento (como setters comuns). Isso significa que, se você escreverMap<? extends KeyType, ValueType>
, o compilador não permitirá que você chame o métodoget(<? extends KeyType>)
e o mapa será inútil. A única solução é fazer com que este método não genérico:get(Object)
.fonte
Compatibilidade com versões anteriores, eu acho.
Map
(ouHashMap
) ainda precisa apoiarget(Object)
.fonte
put
(o que restringe os tipos genéricos). Você obtém compatibilidade com versões anteriores usando tipos brutos. Os genéricos são "aceitos".Eu estava olhando para isso e pensando por que eles fizeram dessa maneira. Não acho que nenhuma das respostas existentes explique por que elas não puderam apenas fazer a nova interface genérica aceitar apenas o tipo adequado para a chave. O motivo real é que, embora eles tenham introduzido genéricos, NÃO criaram uma nova interface. A interface do mapa é o mesmo mapa não genérico antigo, serve apenas como versão genérica e não genérica. Dessa forma, se você tiver um método que aceite um Mapa não genérico, poderá passá-lo
Map<String, Customer>
e ainda funcionará. Ao mesmo tempo, o contrato para get aceita Object, portanto a nova interface também deve suportar esse contrato.Na minha opinião, eles deveriam ter adicionado uma nova interface e implementado tanto na coleção existente, mas decidiram a favor de interfaces compatíveis, mesmo que isso signifique pior design para o método get. Observe que as próprias coleções seriam compatíveis com os métodos existentes, apenas as interfaces não.
fonte
Estamos fazendo uma grande refatoração no momento e estávamos perdendo este get () fortemente tipado para verificar se não perdemos alguns get () com o tipo antigo.
Mas eu achei uma solução alternativa / feia para a verificação do tempo de compilação: crie uma interface de mapa com o get fortemente tipado, containsKey, remova ... e coloque-o no pacote java.util do seu projeto.
Você receberá erros de compilação apenas ao chamar get (), ... com tipos errados, tudo o que os outros parecem bem para o compilador (pelo menos dentro do eclipse kepler).
Não se esqueça de excluir essa interface após a verificação de sua compilação, pois não é isso que você deseja em tempo de execução.
fonte