2008-10-06 20 views
13

Digamos que tengo este tipo en mi solicitud:Actualización de un objeto dentro de un conjunto

public class A { 
    public int id; 
    public B b; 

    public boolean equals(Object another) { return this.id == ((A)another).id; } 
    public int hashCode() { return 31 * id; //nice prime number } 
} 

y una estructura Set<A>. Ahora, tengo un objeto de tipo A y quiero hacer lo siguiente:

  • Si mi A es dentro del conjunto, actualice su campo b para que coincida con mi objeto.
  • De lo contrario, agréguelo al conjunto.

Por lo que comprobar si está allí es bastante fácil (contains), y agregar al conjunto es fácil también. Mi pregunta es esta: ¿cómo obtengo un control para actualizar el objeto dentro? La interfaz Set no tiene un método get, y lo mejor que pude pensar fue eliminar el objeto en el conjunto y agregar el mío. otra, incluso peor, alternativa es atravesar el conjunto con un iterador para intentar localizar el objeto.

Con mucho gusto recibiré mejores sugerencias ... Esto incluye el uso eficiente de otras estructuras de datos.

Yuval = 8-)

EDITAR: Gracias a todos por responder ... Por desgracia no puedo 'aceptar' las mejores respuestas aquí, los que sugieren el uso de un Map, porque al cambiar el tipo de la colección radicalmente para este propósito solo sería un poco extrema (esta colección ya está mapeada a través de Hibernate ...)

+3

Una nota importante aquí: si define iguales, y está utilizando un conjunto, también DEBE DEBER DEBE definir hashCode, para que los elementos que se comparan sean iguales tengan el mismo código hash. –

+0

Si puede responder la pregunta en mi respuesta sobre si necesita más campos que solo id yb, puedo editar mi respuesta para incluir solo el código que necesita. –

+0

¿Por qué "31 * id"? ¿Hay alguna razón convincente para no usar "id" como hashCode? –

Respuesta

14

Dado que un conjunto solo puede contener una instancia de un objeto (como se define por sus métodos equals y hashCode), solo quítalo y luego agrégalo. Si ya había uno, ese otro será eliminado del Conjunto y reemplazado por el que desee.

Tengo un código que hace algo similar: estoy almacenando objetos en el caché para que, en cualquier lugar donde aparezca un objeto en particular en un montón de lugares diferentes en la interfaz gráfica de usuario, siempre sea el mismo. En ese caso, en lugar de usar un Conjunto, estoy usando un Mapa, y luego recibo una actualización, la recupero del Mapa y la actualizo en lugar de crear una nueva instancia.

+2

Eso no es lo que 'add' hace ... Si el conjunto contiene un objeto igual, 'add' devuelve falso. Ver http://java.sun.com/javase/6/docs/api/java/util/Set.html#add(E) – Yuval

+0

Lo cambié a "eliminar y luego agregar". Sigue siendo una forma válida de hacer lo que dijiste que querías, incluso si no era lo que querías decir. –

+0

De acuerdo con @Yuval, Set no reemplazará el objeto antiguo por uno nuevo, sino que mantendrá el anterior solo allí y no lo actualizará. –

11

Realmente desea utilizar un Map<int,A>, no un Set<A>.

A continuación, asigne la ID (aunque también esté almacenada en A) al objeto. Así almacenar nueva es la siguiente:

A a = ...; 
Map<Integer,A> map = new HashMap<Integer,A>(); 
map.put(a.id, a); 

Su algoritmo de actualización completa es:

public static void update(Map<Integer,A> map, A obj) { 
    A existing = map.get(obj.id); 
    if (existing == null) 
    map.put(obj.id, obj); 
    else 
    existing.b = obj.b; 
} 

Sin embargo, podría ser aún más simple. Supongo que tienes más campos que en A que lo que diste. Si este no es el caso, simplemente usando un Map<Integer,B> es en realidad lo que quiere, entonces se derrumba para nada:

Map<Integer,B> map = new HashMap<Integer,B>(); 
// The insert-or-update is just this: 
map.put(id, b); 
+0

Obviamente hay más campos en A ... De hecho, la actualización que pretendo hacer es en los campos dentro de uno de los campos de A! La clave para A no es tan simple tampoco ... = 8-) – Yuval

0

Es un poco fuera de su alcance, pero se olvidó de volver a implementar hashCode(). Cuando anula los iguales, anule hashCode(), incluso en un ejemplo.

Por ejemplo; contains() probablemente saldrá mal cuando tengas una implementación HashSet de Set, ya que HashSet usa el hashCode of Object para localizar el bucket (un número que no tiene nada que ver con la lógica de negocios), y solo iguala() los elementos dentro de ese cangilón.

public class A { 
    public int id; 
    public B b; 
    public int hashCode() {return id;} // simple and efficient enough for small Sets 
    public boolean equals(Object another) { 
    if (object == null || ! (object instanceOf A)) { 
     return false; 
    } 
    return this.id == ((A)another).id; 
    } 
} 
public class Logic { 
    /** 
    * Replace the element in data with the same id as element, or add element 
    * to data when the id of element is not yet used by any A in data. 
    */ 
    public void update(Set<A> data, A element) { 
    data.remove(element); // Safe even if the element is not in the Set 
    data.add(element); 
    } 
} 

EDITAR Yuvalindicated correctamente que Set.add no sobrescribe un elemento existente, pero sólo se suma si el elemento no está aún en la colección (con "es" implementado por iguales)

+0

Se editó la pregunta para agregar hashCode() ... muy bien. – Yuval

+0

Su solución es similar a la de Paul Tomblin, y por desgracia también está mal ... revise el javadoc para Set # add, o simplemente pruébelo. – Yuval

1

¿Qué hay de Mapa <A,A> Sé que es redundante, pero creo que obtendrá el comportamiento que desea. Realmente me encantaría ver a Set tener un método get (Object o) en él.

0

Es posible que desee generar un decorador llamado AAjuste y usar un mapa interno como la estructura de datos de respaldo

class ASet { 
private Map<Integer, A> map; 
public ASet() { 
    map = new HashMap<Integer, A>(); 
} 

public A updateOrAdd(Integer id, int delta) { 
    A a = map.get(a); 
    if(a == null) { 
    a = new A(id); 
    map.put(id,a); 
    } 
    a.setX(a.getX() + delta); 
} 
} 

También puede echar un vistazo a la API encontrado. Si bien eso es mejor para el rendimiento y para la contabilidad de que está trabajando con variables primitivas, expone muy bien esta característica (por ejemplo, map.adjustOrPutValue (clave, initialValue, deltaValue).

6

No creo que pueda hacerlo más fácil que usar eliminar/añadir si está utilizando un conjunto.

set.remove(a); 
    set.add(a); 

Si un juego a se encontró que será eliminado y entonces añadir el nuevo, que ni siquiera necesita el if (set.contains(A)) condicional.

Si tiene un objeto con una ID y un campo actualizado y en realidad no te importan otros aspectos de ese objeto, simplemente deséchalo y reemplázalo.

Si necesita hacer algo más con la A que coincida con esa ID, tendrá que recorrer el Conjunto para encontrarlo o utilizar un Contenedor diferente (como el Mapa como Jason sugirió).

+1

@ 18Raabit Implementé este enfoque, pero una HashMap arrojó una ConcurrentModificationException –

+0

No me gustó cómo se veía ese código, así que terminé haciendo esto. https://stackoverflow.com/a/48740040/728602 –

4

Nadie ha mencionado esto todavía, pero basar hashcode o igual en una propiedad mutable es una de esas realmente grandes cosas que no debes hacer. No te obsesiones con la identidad del objeto después de que abandones el constructor, ya que al hacerlo aumenta tus posibilidades de tener errores realmente difíciles de descifrar. Incluso si no te golpean con errores, el trabajo de contabilidad para asegurarte de que siempre actualiza correctamente todas y cada una de las estructuras de datos que dependen de equals y hashcode siendo consistentes compensará con creces cualquier beneficio percibido de poder simplemente cambiar el Identificación del objeto mientras se ejecuta.

En su lugar, le recomiendo encarecidamente que ingrese el ID a través del constructor y, si necesita cambiarlo, cree una nueva instancia de A. Esto forzará a los usuarios de su objeto a interactuar correctamente con la colección clases (y muchos otros) que dependen del comportamiento inmutable en iguales y hashcode.

Cuestiones relacionadas