Por que Collection.remove (Object o) não é genérico?
Parece que Collection<E>
poderia terboolean remove(E o);
Em seguida, quando você tenta remover acidentalmente (por exemplo) em Set<String>
vez de cada String individual de um Collection<String>
, seria um erro de tempo de compilação em vez de um problema de depuração posteriormente.
java
api
generics
collections
Chris Mazzola
fonte
fonte
Respostas:
Josh Bloch e Bill Pugh se referem a esta edição em Java Puzzlers IV: A ameaça fantasma de referência, Ataque do clone e A vingança da mudança .
Josh Bloch diz (6:41) que eles tentaram gerar o método get do Map, remover o método e outros, mas "simplesmente não funcionou".
Existem muitos programas razoáveis que não poderiam ser gerados se você permitir apenas o tipo genérico da coleção como tipo de parâmetro. O exemplo dado por ele é uma interseção de a
List
deNumber
s e aList
deLong
s.fonte
remove()
(inMap
e inCollection
) não é genérico porque você deve poder passar qualquer tipo de objeto pararemove()
. O objeto removido não precisa ser do mesmo tipo que o objeto para o qual você passaremove()
; requer apenas que sejam iguais. A partir da especificação deremove()
,remove(o)
remove o objetoe
como(o==null ? e==null : o.equals(e))
estátrue
. Observe que não há nada que exijao
ee
seja do mesmo tipo. Isso decorre do fato de oequals()
método receber umObject
parâmetro as, não apenas do mesmo tipo que o objeto.Embora possa ser verdade que muitas classes tenham sido
equals()
definidas para que seus objetos possam ser iguais apenas aos objetos de sua própria classe, esse nem sempre é 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 pergunta, é possível ter umMap<ArrayList, Something>
e para mim chamarremove()
com umLinkedList
argumento as e deve remover a chave que é uma lista com o mesmo conteúdo. Isso não seria possível seremove()
fosse genérico e restringisse seu tipo de argumento.fonte
equals()
método também? Pude ver mais benefícios ao digitar segurança em vez dessa abordagem "libertária". Eu acho que a maioria dos casos com a implementação atual é para erros que entram no nosso código, e não sobre a alegria dessa flexibilidade que oremove()
método traz.equals()
método"?T
deve ser declarado como um parâmetro de tipo na classe eObject
não possui nenhum parâmetro de tipo. Não há como ter um tipo que se refere à "classe declarante".Equality<T>
comequals(T other)
. Então você poderia terremove(Equality<T> o)
eo
é apenas um objeto que pode ser comparado a outroT
.Como se o seu parâmetro type for um curinga, você não poderá usar um método de remoção genérico.
Parece-me que me lembro de me deparar com essa questão com o método get (Object) do Map. O método get, nesse caso, não é genérico, embora deva esperar razoavelmente receber um objeto do mesmo tipo que o primeiro parâmetro de tipo. Percebi que se você está passando o Maps com um curinga como o primeiro parâmetro de tipo, não há como extrair um elemento do Map com esse método, se esse argumento for genérico. Os argumentos curinga não podem ser realmente satisfeitos, porque o compilador não pode garantir que o tipo esteja correto. Especulo que o motivo pelo qual adicionar é genérico é que você deve garantir que o tipo esteja correto antes de adicioná-lo à coleção. No entanto, ao remover um objeto, se o tipo estiver incorreto, ele não corresponderá a nada.
Provavelmente não expliquei muito bem, mas me parece lógico o suficiente.
fonte
Além das outras respostas, há outro motivo pelo qual o método deve aceitar um
Object
, que é predicado. Considere o seguinte exemplo:O ponto é que o objeto que está sendo passado para o
remove
método é responsável por definir oequals
método. A construção de predicados se torna muito simples dessa maneira.fonte
yourObject.equals(developer)
, conforme documentado na API Collections: java.sun.com/javase/6/docs/api/java/util/...equals
método, ou seja, simetria. O método remove está vinculado apenas à sua especificação, desde que seus objetos atendam à especificação equals / hashCode, portanto, qualquer implementação estará livre para fazer a comparação do contrário. Além disso, seu objeto predicado não implementa o.hashCode()
método (não pode ser implementado de forma consistente com iguais), portanto, a chamada de remoção nunca funcionará em uma coleção baseada em Hash (como HashSet ou HashMap.keys ()). O fato de funcionar com ArrayList é pura sorte.Collection.remove
, sem quebrar seu contrato (se a ordem for consistente com igual). E uma ArrayList variada (ou AbstractCollection, eu acho) com a chamada de igual ativada ainda implementaria corretamente o contrato - a culpa é sua se ela não funcionar conforme o esperado, pois você está quebrando oequals
contrato.Suponha que tem uma coleção de
Cat
, e algumas referências de objetos de tiposAnimal
,Cat
,SiameseCat
, eDog
. Perguntar à coleção se ela contém o objeto referido pela referênciaCat
ouSiameseCat
parece razoável. Perguntar se ele contém o objeto referido pelaAnimal
referência pode parecer desonesto, mas ainda é perfeitamente razoável. O objeto em questão pode, afinal, ser umCat
e pode aparecer na coleção.Além disso, mesmo que o objeto seja algo diferente de a
Cat
, não há problema em dizer se ele aparece na coleção - simplesmente responda "não, não aparece". Uma coleção "de estilo de pesquisa" de algum tipo deve ser capaz de aceitar significativamente a referência de qualquer supertipo e determinar se o objeto existe na coleção. Se a referência do objeto passado for de um tipo não relacionado, não há como a coleção a conter, de modo que a consulta não é significativa (de certo modo, ela sempre responderá "não"). No entanto, como não há como restringir os parâmetros a subtipos ou supertipos, é mais prático simplesmente aceitar qualquer tipo e responder "não" a objetos cujo tipo não esteja relacionado ao da coleção.fonte
Comparable
é parametrizado para os tipos com os quais você pode comparar). Então não seria razoável permitir que as pessoas passassem algo de um tipo não relacionado.A
eB
de um tipoX
eY
de outro, tais queA
>B
, eX
>Y
. OuA
>Y
eY
<A
ouX
>B
eB
<X
. Esses relacionamentos só podem existir se as comparações de magnitude conhecerem os dois tipos. Por outro lado, o método de comparação de igualdade de um objeto pode simplesmente se declarar desigual a qualquer coisa de qualquer outro tipo, sem ter que saber nada sobre o outro tipo em questão. Um objeto do tipoCat
pode não ter idéia de se é ... #FordMustang
, mas não deve ter dificuldade em dizer se é igual a esse objeto (a resposta, obviamente, é "não").Eu sempre achei que isso acontecia porque remove () não tem motivos para se importar com o tipo de objeto que você atribui a ele. Independentemente disso, é fácil verificar se esse objeto é um dos que a coleção contém, pois pode chamar igual () em qualquer coisa. É necessário verificar o tipo em add () para garantir que ele contenha apenas objetos desse tipo.
fonte
Foi um compromisso. Ambas as abordagens têm sua vantagem:
remove(Object o)
remove(E e)
traz mais segurança de tipo ao que a maioria dos programas deseja, detectando erros sutis no tempo de compilação, como tentar erroneamente remover um número inteiro de uma lista de curtos.A compatibilidade com versões anteriores sempre foi um dos principais objetivos ao desenvolver a API Java; portanto, o remove (Object o) foi escolhido porque facilitava a geração do código existente. Se a compatibilidade com versões anteriores NÃO tivesse sido um problema, acho que os designers escolheriam remover (E e).
fonte
Remove não é um método genérico, de modo que o código existente usando uma coleção não genérica ainda será compilado e ainda terá o mesmo comportamento.
Consulte http://www.ibm.com/developerworks/java/library/j-jtp01255.html para obter detalhes.
Edit: Um comentarista pergunta por que o método add é genérico. [... removeu minha explicação ...] O segundo comentarista respondeu à pergunta do firebird84 muito melhor do que eu.
fonte
Outro motivo é por causa das interfaces. Aqui está um exemplo para mostrá-lo:
fonte
remove()
não é covariante. A questão, porém, é se isso deve ser permitido.ArrayList#remove()
funciona por meio de igualdade de valor, não de igualdade de referência. Por que você esperaria que aB
fosse igual a umA
? No seu exemplo, pode ser, mas é uma expectativa estranha. Prefiro ver você fornecer umMyClass
argumento aqui.Porque isso quebraria o código existente (pré-Java5). por exemplo,
Agora, você pode dizer que o código acima está errado, mas suponha que o tenha vindo de um conjunto heterogêneo de objetos (ou seja, continha cadeias, número, objetos etc.). Você deseja remover todas as correspondências, o que era legal porque remove iria ignorar as não-seqüências de caracteres porque eram diferentes. Mas se você remover (String o), isso não funcionará mais.
fonte