¿Por qué no Set
proporciona una operación para obtener un elemento que sea igual a otro 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
Puedo preguntar si Set
contiene un elemento igual a bar
, entonces ¿por qué no puedo obtener ese elemento? :(
Para aclarar, el equals
método se anula, pero solo verifica uno de los campos, no todos. Entonces, dos Foo
objetos que se consideran iguales pueden tener valores diferentes, por eso no puedo usarlos foo
.
java
collections
set
equals
foobar
fuente
fuente
SortedSet
y sus implementaciones, que están basadas en mapas (por ejemplo,TreeSet
permite el accesofirst()
).NSSet
) tiene tal método. Se llamamember
y devuelve el objeto dentro del conjunto que compara "igual" con el parámetro delmember
método (que, por supuesto, puede ser un objeto diferente y también tiene propiedades diferentes, que igual no puede comprobar).Respuestas:
No tendría sentido obtener el elemento si es igual. A
Map
es más adecuado para este caso de uso.Si todavía desea encontrar el elemento, no tiene otra opción que usar el iterador:
fuente
Map
es más adecuado (Map<Foo, Foo>
en este caso.)Map<Foo, Foo>
como reemplazo, la desventaja es que un mapa siempre debe almacenar al menos una clave y un valor (y para el rendimiento también debe almacenar el hash), mientras que un conjunto puede escapar simplemente almacenando el valor (y tal vez hash para el rendimiento). Por lo tanto, una buena implementación de conjunto puede ser igualmente rápidaMap<Foo, Foo>
pero usar hasta un 50% menos de memoria. En el caso de Java, no importará, ya que el HashSet se basa internamente en HashMap de todos modos.Para responder a la pregunta precisa " ¿ Por qué no
Set
proporcionar una operación para obtener un elemento que es igual a otro elemento?", La respuesta sería: porque los diseñadores del marco de la colección no eran muy progresistas. No anticiparon su caso de uso muy legítimo, ingenuamente intentaron "modelar la abstracción del conjunto matemático" (del javadoc) y simplemente olvidaron agregar elget()
método útil .Ahora a la pregunta implícita " ¿cómo se obtiene el elemento entonces?": Creo que la mejor solución es usar un en
Map<E,E>
lugar de unSet<E>
, para asignar los elementos a sí mismos. De esa manera, puede recuperar eficientemente un elemento del "conjunto", porque el método get () delMap
buscará el elemento utilizando una tabla hash eficiente o un algoritmo de árbol. Si lo desea, puede escribir su propia implementación deSet
que ofrece elget()
método adicional , encapsulando elMap
.Las siguientes respuestas son, en mi opinión, malas o incorrectas:
"No necesita obtener el elemento, porque ya tiene un objeto igual": la afirmación es incorrecta, como ya mostró en la pregunta. Dos objetos que son iguales todavía pueden tener un estado diferente que no es relevante para la igualdad de objetos. El objetivo es obtener acceso a este estado del elemento contenido en el
Set
, no el estado del objeto utilizado como una "consulta"."No tiene otra opción que usar el iterador": es una búsqueda lineal en una colección que es totalmente ineficiente para conjuntos grandes (irónicamente, internamente
Set
está organizado como un mapa o árbol hash que podría consultarse de manera eficiente). ¡No lo hagas! He visto graves problemas de rendimiento en sistemas de la vida real al usar ese enfoque. En mi opinión, lo terrible delget()
método faltante no es tanto que sea un poco engorroso solucionarlo, sino que la mayoría de los programadores usarán el enfoque de búsqueda lineal sin pensar en las implicaciones.fuente
get()
. En su ejemplo, estaría muy confundido con customerSet.get (thisCustomer). (Mientras que un Mapa, como lo sugieren muchas respuestas) estaría bien con canonicalCustomerMap.get (este cliente). También estaría de acuerdo con un método que tenga un nombre más claro (como el método de miembro de Objective-C en NSSet).Si tiene un objeto igual, ¿por qué necesita el del conjunto? Si es "igual" solo por una clave, una
Map
sería una mejor opción.De todos modos, lo siguiente lo hará:
Con Java 8 esto puede convertirse en una línea:
fuente
Convierta el conjunto en lista y luego use el
get
método de listafuente
Desafortunadamente, el Conjunto predeterminado en Java no está diseñado para proporcionar una operación de "obtención", como explicó jschreiner con precisión.
Las soluciones de usar un iterador para encontrar el elemento de interés (sugerido por dacwe ) o para eliminar el elemento y volver a agregarlo con sus valores actualizados (sugerido por KyleM ) podrían funcionar, pero pueden ser muy ineficientes.
Anular la implementación de iguales para que los objetos no iguales sean "iguales", como lo indicó correctamente David Ogren , puede causar fácilmente problemas de mantenimiento.
Y usar un Mapa como un reemplazo explícito (como lo sugieren muchos), en mi opinión, hace que el código sea menos elegante.
Si el objetivo es obtener acceso a la instancia original del elemento contenido en el conjunto (espero haber entendido correctamente su caso de uso), aquí hay otra posible solución.
Personalmente tuve tu misma necesidad al desarrollar un videojuego cliente-servidor con Java. En mi caso, cada cliente tenía copias de los componentes almacenados en el servidor y el problema era cuando un cliente necesitaba modificar un objeto del servidor.
Pasar un objeto a través de Internet significaba que el cliente tenía diferentes instancias de ese objeto de todos modos. Para hacer coincidir esta instancia "copiada" con la original, decidí usar UUID de Java.
Así que creé una clase abstracta UniqueItem, que automáticamente proporciona una identificación única aleatoria para cada instancia de sus subclases.
Este UUID se comparte entre el cliente y la instancia del servidor, por lo que podría ser fácil hacer coincidirlos simplemente usando un Mapa.
Sin embargo, el uso directo de un Mapa en un caso de uso similar seguía siendo poco elegante. Alguien podría argumentar que usar un Mapa podría ser más complicado de mantener y manejar.
Por estas razones, implementé una biblioteca llamada MagicSet, que hace que el uso de un Mapa sea "transparente" para el desarrollador.
https://github.com/ricpacca/magicset
Al igual que el Java HashSet original, un MagicHashSet (que es una de las implementaciones de MagicSet provistas en la biblioteca) usa un HashMap de respaldo, pero en lugar de tener elementos como claves y un valor ficticio como valores, usa el UUID del elemento como clave y el elemento mismo como valor. Esto no causa sobrecarga en el uso de la memoria en comparación con un HashSet normal.
Además, un MagicSet se puede usar exactamente como un Set, pero con algunos métodos más que proporcionan funcionalidades adicionales, como getFromId (), popFromId (), removeFromId (), etc.
El único requisito para usarlo es que cualquier elemento que desee almacenar en un MagicSet necesita extender la clase abstracta UniqueItem.
Aquí hay un ejemplo de código, imaginando recuperar la instancia original de una ciudad de un MagicSet, dada otra instancia de esa ciudad con el mismo UUID (o incluso solo su UUID).
fuente
Si su conjunto es de hecho un
NavigableSet<Foo>
(como unTreeSet
), yFoo implements Comparable<Foo>
, puede usar(Gracias al comentario de @eliran-malka por la pista).
fuente
Con Java 8 puedes hacer:
Pero tenga cuidado, .get () arroja una NoSuchElementException, o puede manipular un elemento Opcional.
fuente
item->item.equals(theItemYouAreLookingFor)
se puede acortar atheItemYouAreLookingFor::equals
Si solo obtiene uno, esto no será muy eficaz porque pasará sobre todos sus elementos, pero al realizar múltiples recuperaciones en un conjunto grande notará la diferencia.
fuente
Por qué:
Parece que Set juega un papel útil al proporcionar un medio de comparación. Está diseñado para no almacenar elementos duplicados.
Debido a esta intención / diseño, si uno obtuviera () una referencia al objeto almacenado, luego lo mutara, es posible que las intenciones de diseño de Set se frustraran y pudieran causar un comportamiento inesperado.
De los JavaDocs
Cómo:
Ahora que se han introducido Streams, uno puede hacer lo siguiente
fuente
¿Qué pasa con el uso de la clase Arrays?
salida:
elementos uno, dos
fuente
Es mejor que use el objeto Java HashMap para ese propósito http://download.oracle.com/javase/1,5.0/docs/api/java/util/HashMap.html
fuente
Lo sé, esto se ha preguntado y respondido hace mucho tiempo, sin embargo, si alguien está interesado, aquí está mi solución: clase de conjunto personalizado respaldada por HashMap:
http://pastebin.com/Qv6S91n9
Puede implementar fácilmente todos los demás métodos Set.
fuente
He estado allí hecho eso! Si está utilizando Guava, una forma rápida de convertirlo en un mapa es:
fuente
puedes usar la clase Iterator
fuente
Si desea enésimo elemento de HashSet, puede ir con la solución a continuación, aquí he agregado el objeto de ModelClass en HashSet.
fuente
Si observa las primeras líneas de implementación
java.util.HashSet
, verá:De todos modos, se
HashSet
usa de formaHashMap
interna, lo que significa que si solo usa unHashMap
directamente y usa el mismo valor que la clave y el valor, obtendrá el efecto que desea y se ahorrará algo de memoria.fuente
parece que el objeto apropiado para usar es el Interner from guava:
También tiene algunas palancas muy interesantes, como concurrencyLevel o el tipo de referencias utilizadas (vale la pena señalar que no ofrece un SoftInterner que podría ver como más útil que un WeakInterner).
fuente
Debido a que cualquier implementación particular de Set puede o no tener acceso aleatorio .
Siempre puede obtener un iterador y recorrer el conjunto, utilizando el
next()
método de los iteradores para devolver el resultado que desea una vez que encuentre el elemento igual. Esto funciona independientemente de la implementación. Si la implementación NO es un acceso aleatorio (imagínese un conjunto respaldado por una lista vinculada), unget(E element)
método en la interfaz sería engañoso, ya que tendría que iterar la colección para encontrar el elemento a devolver, yget(E element)
parecería que esto implicaría que sería necesario, que el Conjunto pueda saltar directamente al elemento a obtener.contains()
puede o no tener que hacer lo mismo, por supuesto, dependiendo de la implementación, pero el nombre no parece prestarse al mismo tipo de malentendidos.fuente
Sí, use
HashMap
... pero de una manera especializada: la trampa que preveo al tratar de usar aHashMap
como pseudo-Set
es la posible confusión entre elementos "reales" de los elementosMap/Set
"candidatos", es decir, elementos utilizados para probar si unequal
El elemento ya está presente. Esto está lejos de ser infalible, pero te aleja de la trampa:Entonces haz esto:
Pero ... ahora quieres
candidate
que se autodestruya de alguna manera a menos que el programador lo ponga inmediatamente enMap/Set
... querráscontains
"contaminar"candidate
para que su uso a menos que se una alMap
anatema lo haga "anatema" ". Quizás podría hacerSomeClass
implementar una nuevaTaintable
interfaz.Una solución más satisfactoria es un GettableSet , como se muestra a continuación. Sin embargo, para que esto funcione, usted debe estar a cargo del diseño
SomeClass
para hacer que todos los constructores no sean visibles (o ... capaces y dispuestos a diseñar y usar una clase de envoltura para ello):Implementación:
Sus
NoVisibleConstructor
clases se ven así:PS un problema técnico con tal
NoVisibleConstructor
clase: se puede objetar que tal clase es inherentementefinal
, lo que puede ser indeseable. En realidad, siempre puedes agregar unprotected
constructor ficticio sin parámetros :... que al menos permitiría que se compilara una subclase. Debería pensar si necesita incluir otro
getOrCreate()
método de fábrica en la subclase.El paso final es una clase base abstracta (NB "elemento" para una lista, "miembro" para un conjunto) como esta para los miembros de su conjunto (cuando sea posible, de nuevo, alcance para usar una clase envolvente donde la clase no está bajo su control, o ya tiene una clase base, etc.), para una máxima ocultación de la implementación:
... uso es bastante obvio (dentro de su
SomeClass
'sstatic
método de fábrica):fuente
El contrato del código hash deja en claro que:
Entonces su suposición:
está mal y estás rompiendo el contrato. Si observamos el método "contiene" de la interfaz Set, tenemos que:
Para lograr lo que desea, puede usar un Mapa donde defina la clave y almacene su elemento con la clave que define cómo los objetos son diferentes o iguales entre sí.
fuente
Método de ayuda rápida que podría abordar esta situación:
fuente
Seguir puede ser un enfoque
fuente
Intenta usar una matriz:
fuente