Comportamiento extraño del iterador de Java HashSet

Hoy me encontré con un comportamiento extraño con el iterador de HashSet. En el siguiente ejemplo de código, idString utiliza una referencia de objeto devuelta por hs.iterator para llamar al iterador next() método.

In idString2el iterador se llama a través de hs.iterator() y ya no funciona.

Así que asumo que HashSet.iterator() devuelve un nuevo objeto iterador cada vez que se llama. Pero entonces, ¿por qué todavía puedo usar hs.iterator().hasNext() en el ciclo while?

(Tenga en cuenta que el código a continuación es solo un ejemplo :))

import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;

import org.junit.Test;

public class DummyTest {
  static final HashSet<Integer> TEST_DATA = new HashSet<Integer>(
    Arrays.asList(new Integer[] {
      1,2,3,4,5,6,7,8,9,10
    }));

  @Test
  public void testRunTest() {
    // Correct output: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
    System.out.println(idString(TEST_DATA));
    // Only 1, 1, 1, 1, ...
    System.out.println(idString2(TEST_DATA));
  }

  static String idString(HashSet<Integer> hs) {
    Iterator<Integer> it = hs.iterator();
    String res = it.next() + "";
    while (it.hasNext()) {
      res += ", " + it.next();
      System.out.println(res); // debug
    }
    return res;
  }


  static String idString2(HashSet<Integer> hs) {
    Iterator<Integer> it = hs.iterator();
    // Prevent an infinite loop
    int i = 0;
    String res = null;
    res = it.next() + "";
    while (hs.iterator().hasNext() && i++ <= 10) {
      // if replacing hs.iterator() with 'it', it works
      res = res + ", " + hs.iterator().next();
      System.out.println(res); // debug
    }
    return res;
  }
}

preguntado el 12 de junio de 12 a las 21:06

4 Respuestas

Cada vez que llamas iterator() devuelve un Un nuevo iterador, independiente de cualquier otro iterador creado antes. Así que si llamas hs.iterator().next() con amados villancicos que siempre darte el primer elemento, y si llamas hs.iterator().hasNext() en una colección no vacía, será siempre retorno true.

Compara eso con usar it cada vez, que usa un solo iterador en todo momento, por lo tanto, avanza el "cursor" lógico cada vez que llama next().

Respondido el 12 de junio de 12 a las 21:06

Pero, ¿por qué sigue funcionando si solo reemplazo hs.iterator.next() por it.next(), pero no hs.iterator.hasNext()? (Necesita aumentar el límite en el ciclo while, por supuesto) - user3001

@user3001: No está claro qué quiere decir con "aumentar el límite", pero en ese caso estará impresión los valores correctos, pero su bucle nunca funcionaría. hasNext() no cambia el estado del iterador - next() lo hace. - jon skeet

@JonSkeet: ¿Este iterador devuelto por hashset mantendrá el mismo orden de elementos cada vez que se devuelva siempre que no se modifique el hashset? o cambiará en cada llamada? - vishnu viswanath

@sonic: Creo que lo hará, pero al menos lo haría. tratan no confiar ni en eso... - jon skeet

Esto no está claramente documentado en los Javadocs del iterador método (ya sea en el Recaudación or Iterable interfaces) pero todas las colecciones de Java siempre devuelven un nuevo iterador bajo iterador () llamadas.

Por lo tanto, debe reutilizar el iterador que crea en lugar de volver a crear iteradores en cada ejecución de bucle.

Como ejemplo, está el iterador () implementación en ResumenLista:

/**
* Returns an iterator over the elements in this list in proper sequence.
*
* <p>This implementation returns a straightforward implementation of the
* iterator interface, relying on the backing list's {@code size()},
* {@code get(int)}, and {@code remove(int)} methods.
*
* <p>Note that the iterator returned by this method will throw an
* {@link UnsupportedOperationException} in response to its
* {@code remove} method unless the list's {@code remove(int)} method is
* overridden.
*
* <p>This implementation can be made to throw runtime exceptions in the
* face of concurrent modification, as described in the specification
* for the (protected) {@link #modCount} field.
*
* @return an iterator over the elements in this list in proper sequence
*/
public Iterator<E> iterator() {
    return new Itr();
}

Respondido el 12 de junio de 12 a las 21:06

Dicho esto, no puedo ver ningún escenario por el cual alguien incluso comenzaría a pensar que llamar a iterator() varias veces devolvería exactamente el mismo objeto. - Matt

Dado que la documentación no es realmente tan buena y el método no es nada parecido crear iterador or iterador nuevo es realmente difícil resolver esto a menos que lo pruebes tú mismo. - Mauricio Linhares

tu error es que HashSet.iterator() genera un Un nuevo iterador en cada llamada. El nuevo iterador apunta siempre al primer elemento. Por lo tanto tienes que usar el it iterador en el idString2 método.

Respondido el 12 de junio de 12 a las 21:06

Funciona porque a pesar de que está obteniendo una nueva instancia de iterador y verificando si hay un elemento siguiente en el iterador, cada vez que el tiempo verifica la condición.

ex. while (hs.iterator().hasNext() && i++ <= 10) {..

siempre devolverá verdadero porque siempre apuntará al primer elemento, PERO ya asignó una instancia del iterador en esta línea:

Iterador it = hs.iterator();

Entonces, aunque está verificando si hay un elemento siguiente en cada nueva instancia de iterador, está obteniendo el siguiente elemento en la primera instancia de iterador solo asignado en la variable it.

El bucle while finaliza debido a la condición "&& i++ <= 10", por lo que se repite 10 veces y luego deja de hacer el bloque while.

Si esa condición no existiera, obtendría una excepción NoSuchElementException cuando intentara obtener el siguiente elemento inexistente del iterador.

hasNext() solo verifica si hay un elemento siguiente, mientras que next() hace que el cursor apunte al siguiente elemento si existe en el objeto iterador al que se llama.

Respondido el 13 de junio de 12 a las 00:06

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.