¿Por qué el Iterador de Java y el ListIterator apuntan entre elementos?

9

El Javadoc para ListIterator dice:

A ListIteratorno tiene elemento actual; su posición de cursor siempre se encuentra entre el elemento que sería devuelto por una llamada a previous()y el elemento que sería devuelto por una llamada a next().

¿Por qué se ListIteratorimplementó Java para apuntar entre elementos en lugar de en un elemento actual? Parece que esto hace que el código del cliente menos legible cuando se tiene que llamar varias veces getNext(), getPrevious()así que supongo que debe haber una buena razón para la elección.

Como nota al margen, que acabo de escribir una pequeña biblioteca llamada peekable-ArrayList que se extiende ArrayList, Iteratory ListIteratorque proporciona una peekAtNext()y peekAtPrevious()métodos implementados como:

  @Override public synchronized T peekAtNext() {
     T t = next();
     previous();
     return t;
  }
glenviewjeff
fuente
2
Hay un iterador que se puede ver en la biblioteca de guayaba code.google.com/p/guava-libraries
kevin cline
Gracias Kevin! Publiqué una pregunta de seguimiento a esto en SO: ¿Es posible usar ForwardingListIterator de Guava con un PeekingIterator?
glenviewjeff

Respuestas:

12

Por lo que puedo decir, la razón se puede encontrar en la parte de javadoc que no citó (énfasis debajo del mío):

Un iterador para listas que permite al programador recorrer la lista en cualquier dirección, modificar la lista durante la iteración ...

Verá, el propósito previsto es permitir el uso mientras se modifica la lista. Las posibles modificaciones aparentemente incluyen la eliminación de los elementos.

Ahora piense en lo que sucedería si eliminamos un elemento que sería current()para el iterador, suponiendo que ese iterador tuviera una noción de elemento actual. En este contexto, la forma de implementarlo sin una noción de elemento actual tiene bastante sentido para mí, porque de esa manera, el iterador no tiene que preocuparse por la eliminación de elementos.


Es importante tener en cuenta que javadoc no requiere que las implementaciones de interfaz sean seguras para subprocesos.

  • Debido a eso, uno no debería esperar el manejo correcto de las modificaciones realizadas desde diferentes subprocesos; para eso, la implementación tendría que proporcionar medios adicionales para sincronizar el acceso, garantizar la visibilidad, etc., según lo especificado por el Modelo de Memoria Java según JSR 133 .

De lo que ListIterator es capaz es de manejar las modificaciones realizadas desde el mismo hilo al iterar. No todos los iteradores son así, ConcurrentModificationException javadocs advierte específicamente sobre esto:

... Tenga en cuenta que esta excepción no siempre indica que un objeto ha sido modificado simultáneamente por un hilo diferente. Si un solo hilo emite una secuencia de invocaciones de métodos 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 ...

mosquito
fuente
1
También significa que no necesita un producto separado insertBeforey insertAfter, aunque eso no es un problema tan grande como un remove.
Karl Bielefeldt
1
Entonces, ¿es el problema que uno podría eliminar simultáneamente y acceder al elemento actual? ¿No sería este el problema equivalente a la llamada simultánea next()? remove()Sería synchronizedcomo lo haría getCurrent(). ¿Me estoy perdiendo de algo?
glenviewjeff
@glenviewjeff esa es una muy buena observación. También me pregunto cómo se podría manejar CME allí: la intención muy explícita de permitir modificaciones plantea preocupaciones como esa. Supongo que necesito cavar más profundo. Estoy 99.99% seguro de que no está destinado a ser seguro para subprocesos, tal vez solo pueda manejar modificaciones simultáneas realizadas desde el mismo subproceso (no todos los iteradores son capaces de eso, ya sabes)
mosquito
Si te interesa, mira mi edición. Implementé y empaqué una extensión ArrayListpara lograr esto.
glenviewjeff
@glenviewjeff interesante. En su implementación, ¿next () y previous () también se sincronizan? Pregunto porque si no, entonces otro subproceso puede "perforar" algún movimiento inesperado justo en el medio de su actual (), haciendo que se comporte no como el cliente esperaría ... Los métodos como add () probablemente también necesitarían sincronización
mosquito