Obtener un elemento de un conjunto
¿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 en realidad pueden tener valores diferentes, es por eso que no puedo usar simplemente foo
.
Para responder a la pregunta precisa "¿ Por qué no se Set
proporciona una operación para obtener un elemento que sea igual a otro elemento?", la respuesta sería: porque los diseñadores del marco de la colección no fueron muy progresistas. No anticiparon su caso de uso muy legítimo, intentaron ingenuamente "modelar la abstracción del conjunto matemático" (del javadoc) y simplemente se olvidaron de agregar el get()
método útil.
Ahora a la pregunta implícita "¿ cómo se obtiene el elemento entonces?" Creo que la mejor solución es usar a Map<E,E>
en lugar de a Set<E>
para asignar los elementos a sí mismos. De esa manera, puede recuperar eficientemente un elemento del "conjunto", porque el método get() de Map
encontrará el elemento utilizando una tabla hash eficiente o un algoritmo de árbol. Si lo desea, puede escribir su propia implementación que ofrezca el método Set
adicional , encapsulando el archivo .get()
Map
En mi opinión, las siguientes respuestas son malas o incorrectas:
"No necesitas obtener el elemento, porque ya tienes un objeto igual": la afirmación es incorrecta, como ya mostraste en la pregunta. Dos objetos que son iguales aún pueden tener estados diferentes que no son relevantes para la igualdad del objeto. El objetivo es obtener acceso a este estado del elemento contenido en Set
, no al estado del objeto utilizado como "consulta".
"No tiene otra opción que usar el iterador": es decir, una búsqueda lineal sobre una colección que es totalmente ineficiente para conjuntos grandes (irónicamente, internamente está Set
organizado como un mapa hash o un árbol que podría consultarse de manera eficiente). ¡No lo hagas! He visto graves problemas de rendimiento en sistemas de la vida real al utilizar ese enfoque. En mi opinión, lo terrible del get()
método que falta no es tanto que sea un poco engorroso solucionarlo, sino que la mayoría de los programadores utilizarán el enfoque de búsqueda lineal sin pensar en las implicaciones.
No tendría sentido obtener el elemento si es igual. A Map
es más adecuado para este caso de uso.
Si aún deseas encontrar el elemento no tienes otra opción que usar el iterador:
public static void main(String[] args) {
Set<Foo> set = new HashSet<Foo>();
set.add(new Foo("Hello"));
for (Iterator<Foo> it = set.iterator(); it.hasNext(); ) {
Foo f = it.next();
if (f.equals(new Foo("Hello")))
System.out.println("foo found");
}
}
static class Foo {
String string;
Foo(String string) {
this.string = string;
}
@Override
public int hashCode() {
return string.hashCode();
}
@Override
public boolean equals(Object obj) {
return string.equals(((Foo) obj).string);
}
}
Si tienes un objeto igual, ¿por qué necesitas el del conjunto? Si es "igual" sólo por una clave, a Map
sería una mejor opción.
De todos modos, lo siguiente lo hará:
Foo getEqual(Foo sample, Set<Foo> all) {
for (Foo one : all) {
if (one.equals(sample)) {
return one;
}
}
return null;
}
Con Java 8 esto puede convertirse en una sola línea:
return all.stream().filter(sample::equals).findAny().orElse(null);
o (si equals()
funciona como se esperaba):
return all.contains(sample) ? sample : null;