recorrer patron obtener elemento ejemplo buscar java guava foreach

java - patron - ¿Forma idiomática de usar para cada bucle dado un iterador?



recorrer list<> java (8)

Cuando el bucle for forzado (bucle foreach) se agregó a Java, se hizo para trabajar con un objetivo de una matriz o Iterable .

for ( T item : /*T[] or Iterable<? extends T>*/ ) { //use item }

Eso funciona muy bien para las clases de Colección que solo implementan un tipo de iteración, y por lo tanto tienen un solo método iterator() .

Pero me siento increíblemente frustrado por el extraño momento en que quiero usar un iterador no estándar de una clase de Colección. Por ejemplo, hace poco intenté ayudar a alguien a usar un Deque como LIFO / stack pero luego imprimí los elementos en orden FIFO. Me vi obligado a hacer esto:

for (Iterator<T> it = myDeque.descendingIterator(); it.hasNext(); ) { T item = it.next(); //use item }

Pierdo las ventajas del bucle for-each. No se trata sólo de pulsaciones de teclas. No me gusta exponer el iterador si no tengo que hacerlo, ya que es fácil cometer el error de llamarlo. it.next() dos veces, etc.

Ahora, idealmente, creo que el ciclo for-each debería haber aceptado un Iterator también. Pero no es así Entonces, ¿hay una forma idiomática de usar el bucle for-each en estas circunstancias? También me encantaría escuchar sugerencias que usan bibliotecas de colecciones comunes como Guava.

Lo mejor que puedo encontrar en ausencia de un método / clase de ayuda es:

for ( T item : new Iterable<T>() { public Iterator<T> iterator() { return myDeque.descendingIterator(); } } ) { //use item }

Que no vale la pena usar.

Me encantaría ver que la guayaba tenga algo así como Iterables.wrap para hacer esto idiomático, pero no encontré nada de eso. Obviamente, podría rodar mi propia capa Iterator a través de una clase o método de ayuda. ¿Alguna otra idea?

Editar: Como nota al margen, ¿alguien puede dar una razón válida de por qué el for-loop mejorado no debería haber sido capaz de simplemente aceptar un Iterator ? Probablemente sería un gran avance para hacerme vivir con el diseño actual.


¿Por qué el bucle mejorado para simplemente no acepta un iterador?

Quiero reunir algunas de las posibles razones de las diversas respuestas sobre por qué el bucle for-each no acepta simplemente un iterador.

  1. Conveniencia : el bucle for-each se creó en parte para conveniencia de la operación común de realizar una acción dado a cada elemento de una colección. No tiene ninguna obligación o intención de reemplazar el uso explícito de los iteradores (obviamente, si desea eliminar elementos, necesita una referencia explícita al iterador).
  2. Legibilidad : El ciclo for-each for ( Row r : table ) debe ser extremadamente legible como "para cada fila" r "en la tabla ...". Ver for ( Row r : table.backwardsIterator() ) rompe esa legibilidad.
  3. Transparencia : si un objeto es un Iterable y un Iterator , ¿cuál será el comportamiento? Aunque es fácil hacer una regla consistente (por ejemplo, Iterable antes de Iterator), el comportamiento será menos transparente para los desarrolladores. Además, esto tendrá que ser verificado en tiempo de compilación.
  4. Encapsulación / Alcance : Esta es (en mi opinión) la razón más importante. El bucle for-each está diseñado para encapsular el Iterator y limita su alcance al bucle. Esto hace que el bucle sea de "solo lectura" de dos maneras: no expone el iterador, lo que significa que no hay nada (fácilmente) tangible que tenga su estado alterado por el bucle, ni puede alterar el estado del operando en el bucle (como puede interactuar directamente con un iterador a través de remove() ). Si pasa el iterador usted mismo significa que el iterador está expuesto, lo que le hace perder ambos atributos de "solo lectura" del ciclo.

En lugar de crear un descendingIterator , sería mejor escribir un método descendingIterable() para devolver un iterable descendente basado en un deque, que básicamente toma el lugar de su clase anónima. Eso me parece bastante razonable. Según la sugerencia de Colin, la implementación iterable devuelta por este método llamaría descendingIterator en el deque original cada vez que se llame a su propio método iterator() .

Si solo tienes un iterador y quieres mantenerlo así, deberías escribir una implementación de Iterable<T> que envolvió el iterador y lo devolvió exactamente una vez , lanzando una excepción si se llama a iterator() más de una vez. Eso funcionaría, pero claramente sería bastante feo.


La forma idiomática en Java 8 (siendo un lenguaje detallado) es esta:

for (T t : (Iterable<T>) () -> myDeque.descendingIterator()) { // use item }

Es decir, envolver al Iterator en una lambda Iterable . Esto es más o menos lo que hiciste usando una clase anónima, pero es un poco mejor con la lambda.

Por supuesto, siempre puede recurrir al uso de Iterator.forEachRemaining() :

myDeque.descendingIterator().forEachRemaining(t -> { // use item });


Lo que probablemente haría es crear una clase de utilidad llamada Deques que pueda admitir esto, junto con otras utilidades, si así lo desea.

public class Deques { private Deques() {} public static <T> Iterable<T> asDescendingIterable(final Deque<T> deque) { return new Iterable<T>() { public Iterator<T> iterator() { return deque.descendingIterator(); } } } }

Este es otro caso en el que es realmente muy malo que todavía no tengamos lambdas y referencias de métodos. En Java 8, podrá escribir algo como esto dado que el método reference descendingIterator() coincide con la firma de Iterable :

Deque<String> deque = ... for (String s : deque::descendingIterator) { ... }


Los usuarios de Guava pueden hacer ImmutableList.copyOf(Iterator) para convertir de forma segura un Iterator en Iterable. A pesar de la aparente simplicidad de pasar por encima de un iterador, existe la preocupación de que se oculte a los escondites, y la opción más segura es crear una estructura de datos estable como una lista.

Esto también se discute en el Cementerio de ideas :

La mayor preocupación es que generalmente se asume que Iterable puede producir múltiples iteradores independientes. El documento no dice esto, pero el documento de Collection tampoco dice esto, y sin embargo lo asumimos de sus iteradores. Hemos tenido roturas en Google cuando esta suposición fue violada.

La solución más simple es ImmutableList.copyOf(Iterator) , que es bastante rápida, segura y proporciona muchas otras ventajas además.


Por supuesto, la guayaba tiene una solución para el escenario iterable inverso, pero desafortunadamente necesita dos pasos. Iterables.reverse() toma una List como parámetro, no como Iterable .

final Iterable<String> it = Arrays.asList("a", "b", "c"); for(final String item : Iterables.reverse(Lists.newArrayList(it))){ System.out.println(item); }

Salida:

do
segundo
un


Sugiero crear una clase de ayuda con métodos de fábrica que pueda usar así:

import static Iter.*; for( Element i : iter(elements) ) { } for( Element i : iter(o, Element.class) ) { }

Como siguiente paso, el tipo de retorno de iter() podría ser una interfaz fluida para que pueda hacer:

for( Element i : iter(elements).reverse() ) { }

o tal vez

for( Element i : reverse(elements) ) { }

También deberías echarle un vistazo a op4j que resuelve muchos de estos problemas con una API muy agradable.


public class DescendingIterableDequeAdapter<T> implements Iterable<T> { private Deque<T> original; public DescendingIterableDequeAdapter(Deque<T> original) { this.original = original; } public Iterator<T> iterator() { return original.descendingIterator(); } }

Y entonces

for (T item : new DescendingIterableDequeAdapter(deque)) { }

Entonces, para cada caso, necesitarías un adaptador especial. No creo que sea teóricamente posible hacer lo que quieres, porque la instalación debe saber qué métodos de repetición de iteradores existen para que puedan llamarse.

En cuanto a su pregunta adicional, creo que porque el bucle para cada uno estaba destinado a acortar las cosas para los escenarios de propósito general. Y llamar a un método adicional hace que la sintaxis sea más detallada. Podría haber soportado tanto Iterable como Iterator , pero ¿qué pasa si el objeto que se pasa implementa ambos? (Sería extraño, pero aún posible).