while pattern observer collection array java design collections iterator

java - pattern - ¿Es un iterador "malo" un mal diseño?



java iterator() (11)

En general, se considera una mala práctica proporcionar implementaciones de Iterator que sean "infinitas"; es decir, ¿dónde las llamadas a hasNext() siempre (*) devuelven verdadero?

Normalmente digo "sí" porque el código de llamada puede comportarse erráticamente, pero en la implementación a continuación, hasNext() devolverá verdadero a menos que la persona que llama elimine todos los elementos de la lista con la que se inicializó el iterador; es decir, hay una condición de terminación . ¿Crees que este es un uso legítimo de Iterator ? No parece violar el contrato, aunque supongo que se podría argumentar que no es intuitivo.

public class CyclicIterator<T> implements Iterator<T> { private final List<T> l; private Iterator<T> it; public CyclicIterator<T>(List<T> l) { this.l = l; this.it = l.iterator(); } public boolean hasNext() { return !l.isEmpty(); } public T next() { T ret; if (!hasNext()) { throw new NoSuchElementException(); } else if (it.hasNext()) { ret = it.next(); } else { it = l.iterator(); ret = it.next(); } return ret; } public void remove() { it.remove(); } }

(Pedante) EDITAR

Algunas personas han comentado cómo se podría usar un Iterator para generar valores a partir de una secuencia ilimitada, como la secuencia de Fibonacci. Sin embargo, la documentación de Java Iterator indica que un iterador es:

Un iterador sobre una colección.

Ahora podría argumentar que la secuencia de Fibonacci es una colección infinita, pero en Java igualaría la colección con la interfaz java.util.Collection , que ofrece métodos tales como size() lo que implica que una colección debe estar limitada. Por lo tanto, ¿es legítimo usar Iterator como un generador de valores de una secuencia ilimitada?


Considere si tiene una función de generador. Un iterador puede ser una interfaz razonable para un generador, o puede que no, como habrás notado.

Por otro lado, Python tiene generadores que terminan.


Creo que es completamente legítimo : un Iterator es solo una corriente de "cosas". ¿Por qué debería necesariamente limitarse la secuencia?

Muchos otros lenguajes (p. Ej., Scala) tienen el concepto de flujos ilimitados incorporados y pueden repetirse. Por ejemplo, usando scalaz

scala> val fibs = (0, 1).iterate[Stream](t2 => t2._2 -> (t2._1 + t2._2)).map(_._1).iterator fibs: Iterator[Int] = non-empty iterator scala> fibs.take(10).mkString(", ") //first 10 fibonnacci numbers res0: String = 0, 1, 1, 2, 3, 5, 8, 13, 21, 34

EDITAR: En términos del principio de menor sorpresa, creo que depende completamente del contexto. Por ejemplo, ¿qué esperaría que devuelva este método?

public Iterator<Integer> fibonacciSequence();


El objetivo de un Iterator es que es perezoso, es decir, que solo le proporciona tantos objetos como usted solicite. Si un usuario pregunta por todos los objetos de un Iterator infinito, es su problema, no el tuyo.


En realidad, un Iterator proviene de un Iterable , no de una Collection , sea lo que sea lo que diga la API de JavaDoc. Este último amplía el primero, y es por eso que también puede generar un Iterator , pero es perfectamente razonable que un Iterable no sea una Collection . De hecho, hay incluso unas pocas clases de Java que implementan Iterable pero no Collection .

Ahora, en cuanto a las expectativas ... por supuesto, un iterador infinito no debe estar disponible sin una indicación clara de que este sea el caso. Pero, a veces, las expectativas pueden ser engañosas. Por ejemplo, uno podría esperar que un InputStream siempre llegue a su fin, pero eso realmente no es un requisito. Del mismo modo, un Iterator devuelve líneas leídas de tal flujo puede que nunca termine.


Es un uso perfectamente legítimo, siempre que esté debidamente documentado .

Usar el nombre CyclicIterator es una buena idea, ya que deduce que el bucle en el iterador bien podría ser infinito si el caso de salida del bucle no está definido correctamente.


Esto puede ser una semántica, pero el iterador debe devolver diligentemente el siguiente elemento sin tener en cuenta cuándo terminará. El final es un efecto secundario para una colección, aunque puede parecer un efecto secundario común.

Aún así, ¿cuál es la diferencia entre Infinite y una colección con 10 billones de elementos? La persona que llama o los quiere a todos, o no los quiere. Permita que quien llama decida cuántos artículos devolver o cuándo finalizar.

No diría que la persona que llama no pudo usar el constructo for-each. Podría hacerlo, siempre que quiera todos los artículos.

Algo en la documentación para la colección como "puede ser una colección infinita" sería apropiado, pero no "iterador infinito".


Puedo imaginar cualquier cantidad de usos para un iterador infinito. Como, ¿qué pasa con un programa que itera a través de mensajes de estado enviados por otro servidor o un proceso en segundo plano? Mientras que en la vida real el servidor probablemente se detendrá eventualmente, desde el punto de vista del programa, podría seguir leyendo hasta que sea detenido por algo no relacionado con el iterador que llega a su final.

Sin duda sería inusual y, como han dicho otros, debería documentarse cuidadosamente. Pero no lo declararía inaceptable.


Sí. Solo necesita un criterio diferente para cuándo dejar de iterar.

El concepto de listas infinitas es muy común en lenguajes perezosos de evaluación como Haskell y conduce a diseños de programas completamente diferentes.


Si bien yo también creo que es legítimo, me gustaría añadir que un Iterator (o más precisamente: un Iterable produce dicho Iterator ) no funcionaría bien con el for-loop mejorado (también conocido como for-each-loop):

for (Object o : myIterable) { ... }

Como el código dentro de un bucle for mejorado no tiene acceso directo al iterador, no pudo llamar a remove() en el iterador.

Entonces, para finalizar el bucle, tendría que hacer una de las siguientes cosas:

  • Obtenga acceso a la List interna del Iterator y elimine los objetos directamente, lo que posiblemente provoque una ConcurrentModificationException
  • use break para salir del ciclo
  • "use" una Exception para salir del ciclo
  • use return para salir del ciclo

Todas menos la última de esas alternativas no son exactamente la mejor manera de dejar un bucle forzado mejorado.

El bucle for "normal" (o cualquier otro bucle junto con una variable Iterator explícita) funcionaría bien, por supuesto:

for (Iterator it = getIterator(); it.hasNext();) { Object o = it.next() ... }


Si tienes un objeto que necesita un iterador, y le das un iterador infinito, entonces todo debería estar bien. Por supuesto, ese objeto nunca debería requerir que el iterador se detenga. Y ese es el diseño potencialmente malo.

Piensa en un reproductor de música. Le dice que empiece a reproducir pistas en "modo de reproducción continua" y reproduce pistas hasta que le diga que se detenga. Tal vez implemente este modo de reproducción continua mezclando una lista de reproducción para siempre ... hasta que el usuario presione "detener". En este caso, MusicPlayer necesita iterar sobre una lista de reproducción, que podría ser Collection<Track> , para siempre . En este caso, desearía un CyclicIterator<Track> como ha creado o un ShuffleIterator<Track> que elija aleatoriamente la siguiente pista y nunca se agote. Hasta aquí todo bien.

MusicPlayer.play(Iterator<Track> playlist) no se preocuparía si la lista de reproducción finaliza o no. Funciona tanto con un iterador sobre una lista de pistas (reproducir hasta el final, luego detener) y con un ShuffleIterator<Track> sobre una lista de pistas (elija una pista aleatoria para siempre). Todo sigue siendo bueno

¿Qué sucede si alguien intenta escribir MusicPlayer.playContinuously() --que espera reproducir pistas "para siempre" (hasta que alguien presione "detener"), pero acepta un Iterator<Track> como parámetro? Ese sería el problema de diseño. Claramente, playContinuously() necesita un playContinuously() infinito, y si acepta un Iterator simple como su parámetro, entonces eso sería un mal diseño . Sería, por ejemplo, necesario comprobar hasNext() y manejar el caso donde eso devuelve false , aunque nunca debería suceder. Malo.

Así que implemente todos los iteradores infinitos que desee, siempre y cuando sus clientes no dependan de esos iteradores que terminan. Como si su cliente dependiera de que el iterador continúe para siempre, entonces no le dé un Iterator simple .

Esto debería ser obvio, pero no parece ser así.


Un iterador infinito es muy útil cuando se crean datos infinitos, por ejemplo, secuencias recurrentes lineales como la secuencia de Fibonacci .

Entonces está totalmente bien usar tal.