¿Cómo itero y modifico Java Sets?

80

Digamos que tengo un conjunto de enteros y quiero incrementar cada entero en el conjunto. ¿Cómo haría esto?

¿Puedo agregar y eliminar elementos del conjunto mientras lo itero?

¿Necesitaría crear un nuevo conjunto en el que "copiaría y modificaría" los elementos mientras iteraba el conjunto original?

EDITAR: ¿Qué pasa si los elementos del conjunto son inmutables?

Botones840
fuente

Respuestas:

92

Puede eliminar de forma segura de un conjunto durante la iteración con un objeto Iterator; intentar modificar un conjunto a través de su API mientras se itera romperá el iterador. la clase Set proporciona un iterador a través de getIterator ().

sin embargo, los objetos Integer son inmutables; mi estrategia sería iterar a través del conjunto y para cada Entero i, agregar i + 1 a algún nuevo conjunto temporal. Cuando haya terminado de iterar, elimine todos los elementos del conjunto original y agregue todos los elementos del nuevo conjunto temporal.

Set<Integer> s; //contains your Integers
...
Set<Integer> temp = new Set<Integer>();
for(Integer i : s)
    temp.add(i+1);
s.clear();
s.addAll(temp);
Jonathan Weatherhead
fuente
1
¿No sería más fácil dejar el juego original al recolector de basura y continuar usando el nuevo juego? ¿Se recopila el conjunto temporal o el conjunto original, el mío también se queda con el conjunto temporal y no me molesto en mutar el original? Sin embargo, esto no fue posible en mi caso porque el set original era definitivo, así que acepté.
Buttons840
@JonathanWeatherhead ¿Quisiste decir con esa segunda palabra ser en cannotlugar de can? ¿Como en, en You cannot safely removelugar de You can safely remove? Parece una contradicción en ese primer párrafo.
Basil Bourque
@BasilBourque no, quise decir 'puedo'. El método Iterator :: remove () es seguro, como indica la documentación. docs.oracle.com/javase/7/docs/api/java/util/Iterator.html
Jonathan Weatherhead
error: incompatible types: Object cannot be converted to Integer
alhelal
40

Puede hacer lo que quiera si usa un objeto iterador para repasar los elementos de su conjunto. Puede eliminarlos sobre la marcha y está bien. Sin embargo, eliminarlos mientras está en un bucle for (ya sea "estándar", de cada tipo) le traerá problemas:

Set<Integer> set = new TreeSet<Integer>();
    set.add(1);
    set.add(2);
    set.add(3);

    //good way:
    Iterator<Integer> iterator = set.iterator();
    while(iterator.hasNext()) {
        Integer setElement = iterator.next();
        if(setElement==2) {
            iterator.remove();
        }
    }

    //bad way:
    for(Integer setElement:set) {
        if(setElement==2) {
            //might work or might throw exception, Java calls it indefined behaviour:
            set.remove(setElement);
        } 
    }

Según el comentario de @ mrgloom, aquí hay más detalles sobre por qué la forma "mala" descrita anteriormente es, bueno ... mala:

Sin entrar en demasiados detalles sobre cómo Java implementa esto, a un alto nivel, podemos decir que la forma "mala" es mala porque está claramente estipulado como tal en los documentos de Java:

https://docs.oracle.com/javase/8/docs/api/java/util/ConcurrentModificationException.html

estipular, entre otros, que (énfasis mío):

" Por ejemplo, generalmente no está permitido que un subproceso modifique una colección mientras otro subproceso está iterando sobre ella. En general, los resultados de la iteración no están definidos en estas circunstancias. Algunas implementaciones de iteradores (incluidas las de toda la colección de propósito general implementaciones proporcionadas por el JRE) pueden optar por lanzar esta excepción si se detecta este comportamiento "(...)

" Tenga en cuenta que esta excepción no siempre indica que un objeto ha sido modificado simultáneamente por un subproceso diferente. Si un solo subproceso emite una secuencia de invocaciones de método que viola el contrato de un objeto, el objeto puede lanzar esta excepción. Por ejemplo, si un hilo modifica una colección directamente mientras está iterando sobre la colección con un iterador rápido, el iterador lanzará esta excepción ".

Para entrar más en detalles: un objeto que se puede usar en un bucle forEach necesita implementar la interfaz "java.lang.Iterable" (javadoc aquí ). Esto produce un iterador (a través del método "Iterador" que se encuentra en esta interfaz), que se crea una instancia bajo demanda, y contendrá internamente una referencia al objeto Iterable desde el cual fue creado. Sin embargo, cuando se usa un objeto Iterable en un bucle forEach, la instancia de este iterador está oculta para el usuario (no puede acceder a él de ninguna manera).

Esto, junto con el hecho de que un iterador tiene bastante estado, es decir, para hacer su magia y tener respuestas coherentes para sus métodos "next" y "hasNext", necesita que el objeto de respaldo no sea cambiado por otra cosa que no sea el iterador. mientras está iterando, lo hace para que arroje una excepción tan pronto como detecte que algo cambió en el objeto de respaldo mientras está iterando sobre él.

Java llama a esta iteración "a prueba de fallas": es decir, hay algunas acciones, generalmente aquellas que modifican una instancia Iterable (mientras un Iterador está iterando sobre ella). La parte de "falla" de la noción de "falla rápida" se refiere a la capacidad de un iterador para detectar cuándo ocurren tales acciones de "falla". La parte "rápida" de la "falla rápida" (y, que en mi opinión debería llamarse "mejor esfuerzo rápido"), terminará la iteración a través de ConcurrentModificationException tan pronto como pueda detectar que una acción "falla" ocurrir.

Dragón Shivan
fuente
1
¿Puede aclarar por qué falla el mal camino?
mrgloom
@mrgloom He agregado más detalles sobre, básicamente, por qué se puede lanzar una ConcurrentModificationException incluso cuando solo hay un hilo operando en un objeto Iterable
Shivan Dragon
En algunos casos, también obtuvo java.lang.UnsupportedOperationException
Aguid
4

No me gusta mucho la semántica del iterador, por favor considere esto como una opción. También es más seguro ya que publica menos de su estado interno

private Map<String, String> JSONtoMAP(String jsonString) {

    JSONObject json = new JSONObject(jsonString);
    Map<String, String> outMap = new HashMap<String, String>();

    for (String curKey : (Set<String>) json.keySet()) {
        outMap.put(curKey, json.getString(curKey));
    }

    return outMap;

}
snovelli
fuente
0

Puede crear una envoltura mutable del primitivo int y crear un conjunto de esos:

class MutableInteger
{
    private int value;
    public int getValue()
    {
        return value;
    }
    public void setValue(int value)
    {
        this.value = value;
    }
}

class Test
{
    public static void main(String[] args)
    {
        Set<MutableInteger> mySet = new HashSet<MutableInteger>();
        // populate the set
        // ....

        for (MutableInteger integer: mySet)
        {
            integer.setValue(integer.getValue() + 1);
        }
    }
}

Por supuesto, si está utilizando un HashSet, debe implementar el método hash, equals en su MutableInteger, pero eso está fuera del alcance de esta respuesta.

Savvas Dalkitsis
fuente