Por que não Set
fornece uma operação para obter um elemento igual a outro elemento?
Set<Foo> set = ...;
...
Foo foo = new Foo(1, 2, 3);
Foo bar = set.get(foo); // get the Foo element from the Set that equals foo
Posso perguntar se o Set
contém um elemento igual a bar
, então por que não consigo obtê-lo? :(
Para esclarecer, o equals
método é substituído, mas apenas verifica um dos campos, não todos. Portanto, dois Foo
objetos considerados iguais podem realmente ter valores diferentes, é por isso que não posso simplesmente usar foo
.
java
collections
set
equals
foobar
fonte
fonte
SortedSet
e suas implementações, que são baseadas em mapas (por exemplo,TreeSet
permite acessarfirst()
).NSSet
) tem esse método. É chamadomember
e retorna o objeto dentro do conjunto que compara "igual" ao parâmetro domember
método (que pode, é claro, ser um objeto diferente e também ter propriedades diferentes, esse igual pode não ser verificado).Respostas:
Não faria sentido obter o elemento se ele fosse igual. A
Map
é mais adequado para este caso de uso.Se você ainda deseja encontrar o elemento, não tem outra opção senão usar o iterador:
fonte
Map
é mais adequado (Map<Foo, Foo>
neste caso.)Map<Foo, Foo>
como um substituto, a desvantagem é que um mapa sempre deve armazenar pelo menos uma chave e um valor (e para desempenho também deve armazenar o hash), enquanto um conjunto pode se safar apenas armazenando o valor (e talvez hash para desempenho). Portanto, uma boa implementação de conjunto pode ser igualmente rápida,Map<Foo, Foo>
mas usa até 50% menos memória. No caso de Java, isso não importa, pois o HashSet é baseado internamente no HashMap de qualquer maneira.Para responder à pergunta precisa " Por que não
Set
fornece uma operação para obter um elemento que se iguala a outro elemento?", A resposta seria: porque os designers da estrutura de coleção não eram muito prospectivos. Eles não anteciparam seu caso de uso legítimo, tentaram ingenuamente "modelar a abstração matemática do conjunto" (do javadoc) e simplesmente esqueceram de adicionar oget()
método útil .Agora, para a pergunta implícita " como você obtém o elemento então": acho que a melhor solução é usar a em
Map<E,E>
vez de aSet<E>
, para mapear os elementos para si mesmos. Dessa forma, você pode recuperar eficientemente um elemento do "conjunto", porque o método get () doMap
encontrará o elemento usando uma tabela de hash eficiente ou um algoritmo de árvore. Se desejar, você pode escrever sua própria implementaçãoSet
que oferece oget()
método adicional , encapsulando oMap
.As seguintes respostas são ruins ou erradas na minha opinião:
"Você não precisa obter o elemento, porque você já tem um objeto igual": a asserção está errada, como você já mostrou na pergunta. Dois objetos iguais ainda podem ter um estado diferente que não é relevante para a igualdade de objetos. O objetivo é obter acesso a esse estado do elemento contido no
Set
, não ao estado do objeto usado como uma "consulta"."Você não tem outra opção senão usar o iterador": é uma pesquisa linear sobre uma coleção que é totalmente ineficiente para conjuntos grandes (ironicamente, internamente, ela
Set
é organizada como mapa ou árvore de hash que pode ser consultada com eficiência). Não faça isso! Vi sérios problemas de desempenho em sistemas da vida real usando essa abordagem. Na minha opinião, o que é terrível sobre oget()
método ausente não é tanto que seja um pouco complicado contorná-lo, mas a maioria dos programadores usará a abordagem de pesquisa linear sem pensar nas implicações.fonte
get()
. No seu exemplo, eu ficaria muito confuso com o customerSet.get (thisCustomer). (Considerando que, como sugerido por muitas respostas), um mapa seria adequado para canonicalCustomerMap.get (esse cliente). Eu também aceitaria um método com um nome mais claro (como o método membro do Objective-C no NSSet).Se você tem um objeto igual, por que você precisa daquele do conjunto? Se for "igual" apenas por uma chave, uma
Map
seria uma escolha melhor.De qualquer forma, o seguinte será feito:
Com o Java 8, isso pode se tornar um liner:
fonte
Converta o conjunto em lista e use o
get
método da listafonte
Infelizmente, o Conjunto Padrão em Java não foi projetado para fornecer uma operação "get", como o jschreiner explicou com precisão.
As soluções de uso de um iterador para encontrar o elemento de interesse (sugerido pelo dacwe ) ou para remover o elemento e adicioná-lo novamente com seus valores atualizados (sugeridos pelo KyleM ), podem funcionar, mas podem ser muito ineficientes.
Substituir a implementação de iguais para que objetos diferentes sejam "iguais", conforme afirmado corretamente por David Ogren , pode facilmente causar problemas de manutenção.
E usar um mapa como uma substituição explícita (como sugerido por muitos), imho, torna o código menos elegante.
Se o objetivo é obter acesso à instância original do elemento contido no conjunto (espero que eu entenda corretamente seu caso de uso), aqui está outra solução possível.
Pessoalmente, tive a mesma necessidade ao desenvolver um videogame cliente-servidor com Java. No meu caso, cada cliente tinha cópias dos componentes armazenados no servidor e o problema era sempre que um cliente precisava modificar um objeto do servidor.
Passar um objeto pela Internet significava que o cliente tinha instâncias diferentes desse objeto de qualquer maneira. Para combinar essa instância "copiada" com a original, decidi usar os UUIDs Java.
Então, eu criei uma classe abstrata UniqueItem, que automaticamente fornece um ID exclusivo aleatório para cada instância de suas subclasses.
Esse UUID é compartilhado entre o cliente e a instância do servidor; portanto, pode ser fácil combiná-los usando um Mapa.
No entanto, o uso direto de um mapa em um caso de uso semelhante ainda era deselegante. Alguém pode argumentar que usar um mapa pode ser mais complicado de manter e manusear.
Por esses motivos, implementei uma biblioteca chamada MagicSet, que torna o uso de um mapa "transparente" para o desenvolvedor.
https://github.com/ricpacca/magicset
Como o Java HashSet original, um MagicHashSet (que é uma das implementações do MagicSet fornecidas na biblioteca) usa um HashMap de backup, mas em vez de ter elementos como chaves e um valor fictício como valores, ele usa o UUID do elemento como chave e o próprio elemento como valor. Isso não causa sobrecarga no uso da memória em comparação com um HashSet normal.
Além disso, um MagicSet pode ser usado exatamente como um conjunto, mas com mais alguns métodos fornecendo funcionalidades adicionais, como getFromId (), popFromId (), removeFromId (), etc.
O único requisito para usá-lo é que qualquer elemento que você deseja armazenar em um MagicSet precisa estender a classe abstrata UniqueItem.
Aqui está um exemplo de código, imaginando recuperar a instância original de uma cidade de um MagicSet, dada outra instância dessa cidade com o mesmo UUID (ou mesmo apenas seu UUID).
fonte
Se o seu conjunto é de fato um
NavigableSet<Foo>
(como aTreeSet
) eFoo implements Comparable<Foo>
você pode usar(Obrigado ao comentário de @ eliran-malka pela dica.)
fonte
Com o Java 8, você pode:
Mas tenha cuidado, .get () lança uma NoSuchElementException, ou você pode manipular um item opcional.
fonte
item->item.equals(theItemYouAreLookingFor)
pode ser reduzido paratheItemYouAreLookingFor::equals
Se você conseguir apenas isso, não terá muito desempenho, porque você fará um loop sobre todos os seus elementos, mas ao realizar várias recuperações em um grande conjunto, você notará a diferença.
fonte
Por quê:
Parece que Set desempenha um papel útil ao fornecer um meio de comparação. Ele foi projetado para não armazenar elementos duplicados.
Devido a essa intenção / design, se for necessário obter () uma referência ao objeto armazenado e depois modificá-lo, é possível que as intenções de design de Set sejam frustradas e possam causar comportamento inesperado.
Do JavaDocs
Quão:
Agora que o Streams foi introduzido, é possível fazer o seguinte
fonte
Que tal usar a classe Arrays?
saída:
itens um, dois
fonte
É melhor usar o objeto Java HashMap para essa finalidade http://download.oracle.com/javase/1,5.0/docs/api/java/util/HashMap.html
fonte
Eu sei, isso foi perguntado e respondido há muito tempo; no entanto, se alguém estiver interessado, aqui está a minha solução - classe de conjunto personalizado apoiada pelo HashMap:
http://pastebin.com/Qv6S91n9
Você pode implementar facilmente todos os outros métodos Set.
fonte
Foi lá feito isso !! Se você estiver usando o Guava, uma maneira rápida de convertê-lo em um mapa é:
fonte
você pode usar a classe Iterator
fonte
Se você quer o enésimo elemento do HashSet, pode seguir a solução abaixo, aqui eu adicionei o objeto ModelClass no HashSet.
fonte
Se você observar as primeiras linhas da implementação
java.util.HashSet
, verá:Portanto,
HashSet
usa-o deHashMap
maneira interativa, o que significa que, se você usarHashMap
diretamente um e usar o mesmo valor da chave e do valor, obterá o efeito desejado e economizará memória.fonte
parece que o objeto apropriado a ser usado é o Interner da goiaba:
Ele também possui algumas alavancas muito interessantes, como concurrencyLevel ou o tipo de referência usado (pode ser interessante notar que ele não oferece um SoftInterner que eu poderia considerar mais útil que um WeakInterner).
fonte
Porque qualquer implementação específica do conjunto pode ou não ser acesso aleatório .
Você sempre pode obter um iterador e percorrer o conjunto, usando o
next()
método dos iteradores para retornar o resultado desejado depois de encontrar o elemento igual. Isso funciona independentemente da implementação. Se a implementação NÃO for de acesso aleatório (imagine um conjunto suportado por lista vinculada), umget(E element)
método na interface seria enganoso, pois teria que repetir a coleção para encontrar o elemento a ser retornado, e umget(E element)
parecer implicaria que isso seria necessário, que o conjunto possa pular diretamente para o elemento a ser obtido.contains()
pode ou não ter que fazer a mesma coisa, é claro, dependendo da implementação, mas o nome não parece prestar-se ao mesmo tipo de mal-entendidos.fonte
Sim, use
HashMap
... mas de maneira especializada: a armadilha que prevejo ao tentar usar aHashMap
como pseudo-Set
é a possível confusão entre elementos "reais" dos elementosMap/Set
"e candidatos", ou seja, elementos usados para testar se umequal
elemento já está presente. Isso está longe de ser infalível, mas o afasta da armadilha:Então faça o seguinte:
Mas ... agora você quer
candidate
se autodestruir de alguma forma, a menos que o programador realmente o coloque imediatamenteMap/Set
... você gostariacontains
de "manchar" ocandidate
modo que qualquer uso dele, a menos que se junte aoMap
anátema "torna" " Talvez você possaSomeClass
implementar uma novaTaintable
interface.Uma solução mais satisfatória é um GettableSet , como abaixo. No entanto, para que isso funcione, você deve ser responsável pelo design de
SomeClass
, a fim de tornar todos os construtores não visíveis (ou ... capazes e dispostos a projetar e usar uma classe de wrapper para ele):Implementação:
Suas
NoVisibleConstructor
aulas ficam assim:PS: um problema técnico com essa
NoVisibleConstructor
classe: pode-se objetar que essa classe seja inerentementefinal
, o que pode ser indesejável. Na verdade, você sempre pode adicionar umprotected
construtor fictício sem parâmetros :... que pelo menos deixaria uma subclasse compilar. Você teria que pensar se precisa incluir outro
getOrCreate()
método de fábrica na subclasse.A etapa final é uma classe base abstrata (NB "elemento" para uma lista, "membro" para um conjunto) assim para os membros do seu conjunto (quando possível - novamente, o escopo para o uso de uma classe wrapper em que a classe não está sob seu controle, ou já possui uma classe base etc.), para ocultar a implementação máxima:
... uso é bastante óbvio (dentro do seu
SomeClass
'sstatic
método de fábrica):fonte
O contrato do código hash deixa claro que:
Então, sua suposição:
está errado e você está quebrando o contrato. Se olharmos para o método "contains" da interface Set, temos o seguinte:
Para realizar o que você deseja, você pode usar um Mapa no qual define a chave e armazena seu elemento com a chave que define como os objetos são diferentes ou iguais entre si.
fonte
Método de ajuda rápida que pode resolver esta situação:
fonte
A seguir pode ser uma abordagem
fonte
Tente usar uma matriz:
fonte