¿Por qué el Iterator de Java no es un Iterable?


178

¿Por qué la Iteratorinterfaz no se extiende?Iterable ?

El iterator()método simplemente podría regresar this.

¿Es a propósito o simplemente un descuido de los diseñadores de Java?

Sería conveniente poder usar un ciclo for-each con iteradores como este:

for(Object o : someContainer.listSomeObjects()) {
    ....
}

donde listSomeObjects()devuelve un iterador.


1
OK, veo tu punto, pero. aún sería conveniente incluso si rompiera un poco la semántica:] Gracias U por todas las respuestas:]
Łukasz Bownik

Me doy cuenta de que esta pregunta se hizo hace mucho tiempo, pero, ¿te refieres a algún iterador, o simplemente un iterador relacionado con alguna colección?
einpoklum

Respuestas:


67

Porque un iterador generalmente apunta a una sola instancia en una colección. Iterable implica que uno puede obtener un iterador de un objeto para atravesar sus elementos, y no hay necesidad de iterar sobre una sola instancia, que es lo que representa un iterador.


25
+1: una colección es iterable. Un iterador no es iterable porque no es una colección.
S.Lott

50
Si bien estoy de acuerdo con la respuesta, no sé si estoy de acuerdo con la mentalidad. La interfaz Iterable presenta un único método: Iterator <?> Iterator (); En cualquier caso, debería poder especificar un iterador para for-each. No lo compro
Chris K

25
@ S.Lott buen razonamiento circular allí.
yihtserns

16
@ S.Lott Último intento: Colección ∈ Iterable. Iterator ≠ Collection ∴ Iterator ∉ Iterable
yihtserns

27
@ S.Lott: La colección es totalmente irrelevante para esta discusión. Collection es solo una de las muchas implementaciones posibles de Iterable. El hecho de que algo no sea una Colección no tiene relación con si es Iterable.
ColinD

218

Un iterador tiene estado. La idea es que si llama Iterable.iterator()dos veces obtendrá iteradores independientes , de todos modos para la mayoría de los iterables. Claramente, ese no sería el caso en su escenario.

Por ejemplo, generalmente puedo escribir:

public void iterateOver(Iterable<String> strings)
{
    for (String x : strings)
    {
         System.out.println(x);
    }
    for (String x : strings)
    {
         System.out.println(x);
    }
}

Eso debería imprimir la colección dos veces, pero con su esquema, el segundo ciclo siempre finalizaría instantáneamente.


17
@ Chris: Si una implementación devuelve el mismo iterador dos veces, ¿cómo podría cumplir el contrato de Iterator? Si llama iteratory usa el resultado, tiene que iterar sobre la colección, lo que no hará si ese mismo objeto ya ha iterado sobre la colección. ¿Puede dar alguna implementación correcta (que no sea para una colección vacía) donde el mismo iterador se devuelve dos veces?
Jon Skeet

77
Esta es una gran respuesta Jon, realmente tienes el quid del problema aquí. ¡Lástima que esta no sea la respuesta aceptada! El contrato para Iterable está estrictamente definido, pero lo anterior es una gran explicación de las razones por las cuales permitir que Iterator implemente Iterable (para foreach) rompería el espíritu de la interfaz.
joelittlejohn

3
@JonSkeet, mientras que un Iterator <T> tiene estado, el contrato de Iterable <T> no dice nada acerca de poder ser utilizado dos veces para obtener iteradores independientes, aunque es el escenario en el 99% de los casos. Todo lo que dice Iterable <T> es que permite que un objeto sea el objetivo de foreach. Para aquellos de ustedes que no están contentos con que Iterator <T> no sea Iterable <T>, tienen la libertad de crear dicho Iterator <T>. No rompería el contrato ni un poco. Sin embargo, el iterador en sí no debería ser iterable ya que eso lo haría circularmente dependiente y eso sienta las bases para un diseño repulsivo.
Centril

2
@Centril: Correcto. Han editado para indicar que, por lo general, llamar iterabledos veces le dará iteradores independientes.
Jon Skeet

2
Esto llega al corazón de esto. Casi sería posible implementar un Iterable Iterator que implemente .iterator () al reiniciarse, pero este diseño aún se rompería en algunas circunstancias, por ejemplo, si se pasa a un método que toma un Iterable y recorre todos los pares posibles de elementos anidando bucles for-each.
Theodore Murdock

60

Por mis $ 0.02, estoy completamente de acuerdo en que Iterator no debe implementar Iterable, pero creo que el bucle mejorado también debería aceptar. Creo que todo el argumento "hacer iteradores iterables" surge como una solución a un defecto en el lenguaje.

Toda la razón para la introducción del bucle for mejorado fue que "elimina el trabajo pesado y la propensión a errores de los iteradores y las variables de índice al iterar sobre colecciones y matrices" [ 1 ].

Collection<Item> items...

for (Iterator<Item> iter = items.iterator(); iter.hasNext(); ) {
    Item item = iter.next();
    ...
}

for (Item item : items) {
    ...
}

¿Por qué entonces este mismo argumento no es válido para los iteradores?

Iterator<Iter> iter...
..
while (iter.hasNext()) {
    Item item = iter.next();
    ...
}

for (Item item : iter) {
    ...
}

En ambos casos, las llamadas a hasNext () y next () se han eliminado, y no hay ninguna referencia al iterador en el bucle interno. Sí, entiendo que los Iterables se pueden reutilizar para crear múltiples iteradores, pero que todo sucede fuera del ciclo for: dentro del ciclo solo hay una progresión hacia adelante un elemento a la vez sobre los elementos devueltos por el iterador.

Además, permitir esto también facilitaría el uso del bucle for para Enumeraciones, que, como se ha señalado en otra parte, son análogos a Iteradores, no Iterables.

Por lo tanto, no haga que Iterator implemente Iterable, pero actualice el bucle for para aceptar tampoco.

Salud,


66
Estoy de acuerdo. Teóricamente podría haber confusión al obtener un iterador, usar parte de él y luego ponerlo en un foreach (romper el "cada" contrato de foreach), pero no creo que sea una razón suficiente para no tener esta característica.
Bart van Heukelom

Ya hemos actualizado / mejorado el ciclo para aceptar matrices en lugar de hacer que las matrices sean iterables. ¿Puedes racionalizar esta decisión?
Val

Voté esta respuesta y fue un error. El iterador tiene estado, si promueve la iteración sobre un iterador con la for(item : iter) {...}sintaxis, provocará un error cuando el mismo iterador se repita dos veces. Imagine que Iteratorse pasa al iterateOvermétodo en lugar de Iterableen este ejemplo .
Ilya Silvestrov

2
Realmente no importa si usa el estilo for (String x : strings) {...}o while (strings.hasNext()) {...}: si intenta recorrer un iterador dos veces la segunda vez no obtendrá resultados, por lo que no lo veo en sí mismo como un argumento en contra de permitir la sintaxis mejorada. La respuesta de Jon es diferente, porque allí está mostrando cómo envolver una Iteratoren una Iterablecausaría problemas, ya que en ese caso esperarías poder reutilizarla tantas veces como quisieras.
Barney

17

Como señalaron otros, Iteratory Iterableson dos cosas diferentes.

Además, las Iteratorimplementaciones son anteriores a los bucles.

También es trivial superar esta limitación con un método de adaptador simple que se ve así cuando se usa con importaciones de métodos estáticos:

for (String line : in(lines)) {
  System.out.println(line);
}

Implementación de muestra:

  /**
   * Adapts an {@link Iterator} to an {@link Iterable} for use in enhanced for
   * loops. If {@link Iterable#iterator()} is invoked more than once, an
   * {@link IllegalStateException} is thrown.
   */
  public static <T> Iterable<T> in(final Iterator<T> iterator) {
    assert iterator != null;
    class SingleUseIterable implements Iterable<T> {
      private boolean used = false;

      @Override
      public Iterator<T> iterator() {
        if (used) {
          throw new IllegalStateException("SingleUseIterable already invoked");
        }
        used = true;
        return iterator;
      }
    }
    return new SingleUseIterable();
  }

En Java 8, adaptar una Iteratora una se Iterablevuelve más simple:

for (String s : (Iterable<String>) () -> iterator) {

declaraste una clase en una función; ¿Me estoy perdiendo de algo? Pensé que esto era ilegal.
activedecay

Una clase se puede definir en un bloque en Java. Se llama una clase local
Colin D Bennett

3
Gracias por elfor (String s : (Iterable<String>) () -> iterator)
AlikElzin-kilaka

8

Como otros han dicho, un Iterable se puede llamar varias veces, devolviendo un nuevo iterador en cada llamada; un iterador se usa solo una vez. Entonces están relacionados, pero tienen diferentes propósitos. Sin embargo, frustrantemente, el método "compacto para" funciona solo con un iterable.

Lo que describiré a continuación es una forma de tener lo mejor de ambos mundos: devolver un Iterable (para una sintaxis más agradable) incluso cuando la secuencia de datos subyacente es única.

El truco consiste en devolver una implementación anónima de Iterable que realmente active el trabajo. Entonces, en lugar de hacer el trabajo que genera una secuencia única y luego devolver un iterador sobre eso, devuelve un Iterable que, cada vez que se accede, vuelve a hacer el trabajo. Puede parecer un desperdicio, pero a menudo solo llamarás al Iterable de todos modos, e incluso si lo llamas varias veces, todavía tiene una semántica razonable (a diferencia de un contenedor simple que hace que un Iterador "parezca" un Iterable, esto ganó ' t falla si se usa dos veces).

Por ejemplo, supongamos que tengo un DAO que proporciona una serie de objetos de una base de datos y quiero proporcionar acceso a eso a través de un iterador (por ejemplo, para evitar crear todos los objetos en la memoria si no son necesarios). Ahora podría devolver un iterador, pero eso hace que usar el valor devuelto en un bucle sea feo. Entonces, en cambio, envuelvo todo en un anon Iterable:

class MetricDao {
    ...
    /**
     * @return All known metrics.
     */
    public final Iterable<Metric> loadAll() {
        return new Iterable<Metric>() {
            @Override
            public Iterator<Metric> iterator() {
                return sessionFactory.getCurrentSession()
                        .createQuery("from Metric as metric")
                        .iterate();
            }
        };
    }
}

Esto se puede usar en un código como este:

class DaoUser {
    private MetricDao dao;
    for (Metric existing : dao.loadAll()) {
        // do stuff here...
    }
}

lo que me permite usar el bucle compacto para mantener el uso de memoria incremental.

Este enfoque es "vago": el trabajo no se realiza cuando se solicita el Iterable, sino solo más tarde cuando se repite el contenido, y debe ser consciente de las consecuencias de eso. En el ejemplo con un DAO que significa iterar sobre los resultados dentro de la transacción de la base de datos.

Así que hay varias advertencias, pero esto puede ser una expresión útil en muchos casos.


bonitas y ranchos, pero su returning a fresh Iterator on each calldebe ir acompañado de por qué esto es para evitar la emisión de concurrencia ...
Anirudha

7

Increíblemente, nadie más ha dado esta respuesta todavía. A continuación, le mostramos cómo puede iterar "fácilmente" sobre una Iteratormediante el nuevo Iterator.forEachRemaining()método Java 8 :

Iterator<String> it = ...
it.forEachRemaining(System.out::println);

Por supuesto, hay una solución "más simple" que funciona con el bucle foreach directamente, envolviendo el Iteratoren una Iterablelambda:

for (String s : (Iterable<String>) () -> it)
    System.out.println(s);

5

Iteratores una interfaz que te permite iterar sobre algo. Es una implementación de moverse a través de una colección de algún tipo.

Iterable es una interfaz funcional que denota que algo contiene un iterador accesible.

En Java8, esto hace la vida bastante fácil ... Si tiene un Iteratorpero necesita Iterableuno, simplemente puede hacer:

Iterator<T> someIterator;
Iterable<T> = ()->someIterator;

Esto también funciona en el ciclo for:

for (T item : ()->someIterator){
    //doSomething with item
}

2

Estoy de acuerdo con la respuesta aceptada, pero quiero agregar mi propia explicación.

  • El iterador representa el estado de desplazamiento, por ejemplo, puede obtener el elemento actual de un iterador y avanzar al siguiente.

  • Iterable representa una colección que podría atravesarse, puede devolver tantos iteradores como desee, cada uno representando su propio estado de desplazamiento, un iterador puede estar apuntando al primer elemento, mientras que otro puede estar apuntando al tercer elemento.

Sería bueno si Java for loop acepta tanto Iterator como Iterable.


2

Para evitar la dependencia del java.utilpaquete

De acuerdo con el JSR original, un bucle mejorado para el lenguaje de programación Java ™ , las interfaces propuestas:

  • java.lang.Iterable
  • java.lang.ReadOnlyIterator
    (se propuso adaptarlo java.util.Iterator, pero aparentemente esto nunca sucedió)

... fueron diseñados para usar el java.langespacio de nombres del paquete en lugar de java.util.

Para citar el JSR:

Estas nuevas interfaces sirven para evitar la dependencia del lenguaje en java.util que de lo contrario resultaría.


Por cierto, el viejo java.util.Iterableganó un nuevo forEachmétodo en Java 8+, para usar con la sintaxis lambda (pasando a Consumer).

Aquí hay un ejemplo. La Listinterfaz extiende la Iterableinterfaz, ya que cualquier lista lleva un forEachmétodo.

List
.of ( "dog" , "cat" , "bird" )
.forEach ( ( String animal ) -> System.out.println ( "animal = " + animal ) );

2

También veo a muchos haciendo esto:

public Iterator iterator() {
    return this;
}

¡Pero eso no lo hace correcto! ¡Este método no sería lo que quieres!

Se iterator()supone que el método devuelve un nuevo iterador a partir de cero. Entonces uno necesita hacer algo como esto:

public class IterableIterator implements Iterator, Iterable {

  //Constructor
  IterableIterator(SomeType initdata)
  {
    this.initdata = iter.initdata;
  }
  // methods of Iterable

  public Iterator iterator() {
    return new IterableIterator(this.intidata);
  }

  // methods of Iterator

  public boolean hasNext() {
    // ...
  }

  public Object next() {
    // ...
  }

  public void remove() {
    // ...
  }
}

La pregunta es: ¿habría alguna forma de hacer que una clase abstracta realice esto? Para obtener un IterableIterator solo es necesario implementar los dos métodos next () y hasNext ()


1

Si vino aquí en busca de una solución alternativa, puede usar IteratorIterable . (disponible para Java 1.6 y superior)

Ejemplo de uso (invertir un vector).

import java.util.Vector;
import org.apache.commons.collections4.iterators.IteratorIterable;
import org.apache.commons.collections4.iterators.ReverseListIterator;
public class Test {
    public static void main(String ... args) {
        Vector<String> vs = new Vector<String>();
        vs.add("one");
        vs.add("two");
        for ( String s: vs ) {
            System.out.println(s);
        }
        Iterable<String> is
            = new IteratorIterable(new ReverseListIterator(vs));
        for ( String s: is ) {
            System.out.println(s);
        }
    }
}

huellas dactilares

one
two
two
one

0

En aras de la simplicidad, Iterador e Iterable son dos conceptos distintos, Iterable es simplemente una abreviatura de "Puedo devolver un Iterador". Creo que tu código debería ser:

for(Object o : someContainer) {
}

con alguna instancia de Contenedor SomeContainer extends Iterable<Object>




0

Los iteradores tienen estado, tienen un elemento "siguiente" y se "agotan" una vez que se repiten. Para ver dónde está el problema, ejecute el siguiente código, ¿cuántos números se imprimen?

Iterator<Integer> iterator = Arrays.asList(1,2,3).iterator();
Iterable<Integer> myIterable = ()->iterator;
for(Integer i : myIterable) System.out.print(i);
System.out.println();
for(Integer i : myIterable) System.out.print(i);

-1

Puedes probar el siguiente ejemplo:

List ispresent=new ArrayList();
Iterator iterator=ispresent.iterator();
while(iterator.hasNext())
{
    System.out.println(iterator.next());
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.