2008-10-31 41 views
28

Tengo problemas con Iterator.remove() invocado en un HashSet.HashSet.remove() y Iterator.remove() no funcionan

Tengo un conjunto de objetos marcados con el tiempo. Antes de agregar un nuevo elemento al conjunto, recorro el conjunto, identifico una versión anterior de ese objeto de datos y lo elimino (antes de agregar el nuevo objeto). la marca de tiempo está incluida en hashCode y es igual a(), pero no es igual aData().

for (Iterator<DataResult> i = allResults.iterator(); i.hasNext();) 
{ 
    DataResult oldData = i.next(); 
    if (data.equalsData(oldData)) 
    { 
     i.remove(); 
     break; 
    } 
} 
allResults.add(data) 

Lo curioso es que i.remove() falla en silencio (sin excepción) para algunos de los elementos del conjunto. He verificado

  • La línea i.remove() se llama en realidad. Puedo llamarlo desde el depurador directamente en el punto de interrupción en Eclipse y todavía no se puede cambiar el estado de Set

  • DataResult es un objeto inmutable por lo que no puede haber cambiado después de haber sido agregado originalmente al conjunto.

  • Los métodos equals y hashCode() usan @Override para garantizar que sean los métodos correctos. Las pruebas unitarias verifican este trabajo.

  • Esto también falla si solo uso una instrucción for y Set.remove en su lugar. (por ejemplo, recorrer los elementos, encontrar el elemento en la lista, luego llamar a Set.remove (oldData) después del bucle).

  • He probado en el JDK 5 y JDK 6.

pensé que debe estar pasando algo básico, pero después de pasar un tiempo considerable en esta mi colega y yo está confundido. ¿Alguna sugerencia de cosas para verificar?

EDIT:

Ha habido preguntas - es DataResult verdad inmutable. Sí. No hay setters Y cuando se recupera el objeto Date (que es un objeto mutable), se hace creando una copia.

public Date getEntryTime() 
{ 
    return DateUtil.copyDate(entryTime); 
} 

public static Date copyDate(Date date) 
{ 
    return (date == null) ? null : new Date(date.getTime()); 
} 

hacer otras modificaciones (un poco más tarde): Para el registro - DataResult no era inmutable! Hacía referencia a un objeto que tenía un código hash que cambiaba cuando se conservaba en la base de datos (mala práctica, lo sé). Resultó que si se creaba un DataResult con un subobjeto transitorio, y el subobjeto se conservaba, se cambiaba el hashcode DataResult.

Muy sutil: miré esto muchas veces y no noté la falta de inmutabilidad.

+0

Dos posibilidades. 1. Usted dice que DataResult es inmutable. ¿Es seguro asumir que el constructor establece los valores y no hay ningún método establecido? 2. Tus iguales y hashcode no funcionan como esperabas. ¿Puedes publicar el código para esos dos? –

Respuesta

35

Tenía mucha curiosidad acerca de esto todavía, y escribió el siguiente ensayo:

import java.util.HashSet; 
import java.util.Iterator; 
import java.util.Random; 
import java.util.Set; 

public class HashCodeTest { 
    private int hashCode = 0; 

    @Override public int hashCode() { 
     return hashCode ++; 
    } 

    public static void main(String[] args) { 
     Set<HashCodeTest> set = new HashSet<HashCodeTest>(); 

     set.add(new HashCodeTest()); 
     System.out.println(set.size()); 
     for (Iterator<HashCodeTest> iter = set.iterator(); 
       iter.hasNext();) { 
      iter.next(); 
      iter.remove(); 
     } 
     System.out.println(set.size()); 
    } 
} 

que se traduce en:

1 
1 

Si el valor hashCode() de un objeto ha cambiado desde que se agregó al HashSet, parece hacer que el objeto no se pueda quitar.

No estoy seguro de si ese es el problema con el que se está encontrando, pero es algo a investigar si decide volver a visitar esto.

+0

Gracias-- eso es muy apreciado. Sospecho que tienes razón. Volveré a revisar mis pruebas de unidad para equals/hashCode en el objeto base. –

+1

Seis años después y esta respuesta me ha ahorrado horas adicionales de interacción con el jefe. +1. + allthe1s – Smalltown2k

+0

+1; Esta solución solo me ayudó a resolver un error frustrante al compilar un conjunto de subgrafos. Me gustaría agregar que la razón por la que esto ocurre parece ser que HashSet ajusta HashMap. Los valores en el hash se tratan como claves en lugar de valores. Por supuesto, las claves no pueden ser mutables, pero es frustrante que se viole la noción intuitiva de que los conjuntos están compuestos por iteración ... – sadakatsu

1

Ha intentado algo así como

boolean removed = allResults.remove(oldData) 
if (!removed) // COMPLAIN BITTERLY! 

En otras palabras, quitar el objeto del conjunto y romper el bucle. Eso no causará que el Iterator se queje. No creo que esto es una solución a largo plazo, pero probablemente le dará alguna información sobre las hashCode, equals y equalsData métodos

-2

Si hay dos entradas con los mismos datos, sólo se sustituye una de ellas tiene ... usted representa eso? Y por si acaso, ¿ha probado otra estructura de datos de colección que no utiliza un código hash, por ejemplo, una lista?

+2

Un conjunto no permite engañar ... –

+0

es correcto, pero los engaños se definen por el método equals(), que en este caso usa los datos + marca de tiempo. –

2

¿Está absolutamente seguro de que DataResult es inmutable? ¿Cuál es el tipo de marca de tiempo? Si es un java.util.Date, ¿está haciendo copias cuando está inicializando DataResult? Tenga en cuenta que java.util.Date es mutable.

Por ejemplo:

Date timestamp = new Date(); 
DataResult d = new DataResult(timestamp); 
System.out.println(d.getTimestamp()); 
timestamp.setTime(System.currentTimeMillis()); 
System.out.println(d.getTimestamp()); 

imprimiría dos momentos diferentes.

También ayudaría si pudiera publicar algún código fuente.

+0

@Jack Leow: Es un gran comentario. Sin embargo, estoy copiando el objeto de fecha antes de exponer a través del getter. (He estado leyendo la edición 2 de Effectiva Java de Bloch recientemente). Entonces DataResult es verdaderamente inmutable. –

+0

copiando el valor o la referencia? –

+0

@Blade: crea una nueva instancia de Fecha. Ver editar a la pregunta original. –

6

Bajo las cubiertas, HashSet usa HashMap, que llama a HashMap.removeEntryForKey (Object) cuando se llama a HashSet.remove (Object) o Iterator.remove(). Este método usa tanto hashCode() como equals() para validar que está eliminando el objeto apropiado de la colección.

Si tanto Iterator.remove() como HashSet.remove (Object) no funcionan, entonces algo definitivamente está mal con los métodos equals() o hashCode(). Publicar el código para estos sería útil en el diagnóstico de su problema.

-4

No estoy al tanto de mi Java, pero sé que no puede eliminar un elemento de una colección cuando está iterando sobre esa colección en .NET, aunque .NET arrojará una excepción si atrapa esta. ¿Podría ser este el problema?

+0

Si bien a veces es cierto que no puede eliminar un elemento de un respaldo de colección de un iterador (podría lanzarse una ConcurrentModificationException), puede eliminar el elemento directamente a través del mismo iterador, si es compatible con la acción, lo que hace para HashSet. –

+0

Gracias por educarme. :) – David

+0

Y los iteradores que no son compatibles con la eliminación arrojan UnsupportedOperationException en cualquier intento de eliminación(). –

2

Gracias por la ayuda. Sospecho que el problema debe ser con equals() y hashCode() como lo sugiere spencerk. Los revisé en mi depurador y con pruebas unitarias, pero me falta algo.

Terminé haciendo una solución alternativa: copiar todos los elementos excepto uno para un nuevo conjunto. Para las patadas, utilicé Apache Commons CollectionUtils.

Set<DataResult> tempResults = new HashSet<DataResult>(); 
    CollectionUtils.select(allResults, 
      new Predicate() 
      { 
       public boolean evaluate(Object oldData) 
       { 
        return !data.equalsData((DataResult) oldData); 
       } 
      } 
      , tempResults); 
    allResults = tempResults; 

Voy a parar aquí-- demasiado trabajo para simplificar a un simple caso de prueba. Pero la ayuda es muy apreciada.

1

Es casi seguro que los códigos hash no coincidan con los datos antiguos y nuevos que son "iguales()". Me encontré con este tipo de cosas antes y esencialmente terminas arrojando hashcodes para cada objeto y la representación de la cadena y tratando de descubrir por qué está sucediendo la falta de coincidencia.

Si está comparando elementos de la base de datos pre/post, a veces pierde los nanosegundos (dependiendo de su tipo de columna de base de datos) que pueden hacer que cambien los códigos hash.

2

Todos deben tener cuidado con cualquier colección de Java que capte sus elementos secundarios mediante hashcode, en el caso de que el código de hash de su hijo dependa de su estado mutable.Un ejemplo:

HashSet<HashSet<?>> or HashSet<AbstaractSet<?>> or HashMap variant: 

HashSet recupera un elemento por su código hash, pero su elemento tipo es un HashSet y hashSet.hashCode depende del estado de su artículo.

Código

para el caso:

HashSet<HashSet<String>> coll = new HashSet<HashSet<String>>(); 
HashSet<String> set1 = new HashSet<String>(); 
set1.add("1"); 
coll.add(set1); 
print(set1.hashCode()); //---> will output X 
set1.add("2"); 
print(set1.hashCode()); //---> will output Y 
coll.remove(set1) // WILL FAIL TO REMOVE (SILENTLY) 

La razón es método remove de HashSet usa HashMap y se identifica teclas por hashCode, mientras hashCode de AbstractSet es dinámica y depende de las propiedades mutables de sí mismo.

Cuestiones relacionadas