Pourquoi Java's Iterator n'est-il pas un Iterable?

178

Pourquoi l' Iteratorinterface ne s'étend pasIterable ?

La iterator()méthode pourrait simplement retourner this.

Est-ce intentionnel ou simplement une erreur des concepteurs de Java?

Il serait pratique de pouvoir utiliser une boucle for-each avec des itérateurs comme ceci:

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

listSomeObjects()renvoie un itérateur.

Łukasz Bownik
la source
1
OK - je vois votre point mais. ce serait toujours pratique même si cela brisait un peu la sémantique:] Merci pour toutes les réponses:]
Łukasz Bownik
Je me rends compte que cette question a été posée il y a longtemps, mais - voulez-vous dire un itérateur, ou juste un itérateur relatif à une collection?
einpoklum

Réponses:

67

Parce qu'un itérateur pointe généralement vers une seule instance dans une collection. Iterable implique que l'on peut obtenir un itérateur à partir d'un objet pour traverser ses éléments - et il n'est pas nécessaire d'itérer sur une seule instance, ce que représente un itérateur.

PaulJWilliams
la source
25
+1: Une collection est itérable. Un itérateur n'est pas itérable car ce n'est pas une collection.
S.Lott
50
Bien que je sois d'accord avec la réponse, je ne sais pas si je suis d'accord avec la mentalité. L'interface Iterable présente une seule méthode: Iterator <?> Iterator (); Dans tous les cas, je devrais pouvoir spécifier un itérateur pour for-each. Je ne l'achète pas.
Chris K
25
@ S.Lott beau raisonnement circulaire là-bas.
yihtserns
16
@ S.Lott Dernière tentative: Collection ∈ Iterable. Iterator ≠ Collection ∴ Iterator ∉ Iterable
yihtserns
27
@ S.Lott: La collection n'a aucun rapport avec cette discussion. La collection n'est que l'une des nombreuses implémentations possibles d'Iterable. Le fait que quelque chose ne soit pas une collection n'a aucune incidence sur la question de savoir si c'est un Iterable.
ColinD le
218

Un itérateur est avec état. L'idée est que si vous appelez Iterable.iterator()deux fois, vous obtiendrez des itérateurs indépendants - du moins pour la plupart des itérables. Ce ne serait clairement pas le cas dans votre scénario.

Par exemple, je peux généralement écrire:

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

Cela devrait imprimer la collection deux fois - mais avec votre schéma, la deuxième boucle se terminerait toujours instantanément.

Jon Skeet
la source
17
@Chris: Si une implémentation retourne deux fois le même itérateur, comment diable pourrait-elle remplir le contrat d'Iterator? Si vous appelez iteratoret utilisez le résultat, il doit itérer sur la collection - ce qu'il ne fera pas si ce même objet a déjà itéré sur la collection. Pouvez-vous donner une implémentation correcte (autre que pour une collection vide) où le même itérateur est retourné deux fois?
Jon Skeet
7
C'est une excellente réponse Jon, vous avez vraiment le nœud du problème ici. Dommage que ce ne soit pas la réponse acceptée! Le contrat pour Iterable est strictement défini, mais ce qui précède est une excellente explication des raisons pour lesquelles autoriser Iterator à implémenter Iterable (pour foreach) briserait l'esprit de l'interface.
joelittlejohn
3
@JonSkeet alors qu'un Iterator <T> est avec état, le contrat de Iterable <T> ne dit rien sur le fait de pouvoir être utilisé deux fois pour obtenir des itérateurs indépendants, même si c'est le scénario dans 99% des cas. Tout ce qu'Iterable <T> dit, c'est qu'il permet à un objet d'être la cible de foreach. Pour ceux d'entre vous qui ne sont pas satisfaits du fait qu'Iterator <T> ne soit pas un Iterable <T>, vous êtes libre de créer un tel Iterator <T>. Cela ne romprait pas du tout le contrat. Cependant - Iterator lui-même ne devrait pas être Iterable car cela le rendrait circulairement dépendant et cela jette les bases d'un design dégoûtant.
Centril
2
@Centril: C'est vrai. Avoir modifié pour indiquer que généralement appeler iterabledeux fois vous donnera des itérateurs indépendants.
Jon Skeet
2
Cela va au cœur du problème. Il serait presque possible d'implémenter un Iterable Iterator qui implémente .iterator () en se réinitialisant, mais cette conception serait toujours interrompue dans certaines circonstances, par exemple, si elle était passée à une méthode qui prend un Iterable et boucle sur toutes les paires possibles d'éléments en imbriquant pour chaque boucle.
Theodore Murdock
60

Pour mon 0,02 $, je suis tout à fait d'accord pour dire qu'Iterator ne devrait pas implémenter Iterable, mais je pense que la boucle for améliorée devrait non plus accepter. Je pense que tout l'argument «rendre les itérateurs itérables» apparaît comme une solution à un défaut de langage.

La seule raison de l'introduction de la boucle for améliorée était qu'elle "élimine la corvée et la propension aux erreurs des itérateurs et des variables d'index lors de l'itération sur des collections et des tableaux" [ 1 ].

Collection<Item> items...

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

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

Pourquoi alors ce même argument ne vaut-il pas pour les itérateurs?

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

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

Dans les deux cas, les appels à hasNext () et next () ont été supprimés et il n'y a aucune référence à l'itérateur dans la boucle interne. Oui, je comprends que les Iterables peuvent être réutilisés pour créer plusieurs itérateurs, mais que tout se passe en dehors de la boucle for: à l'intérieur de la boucle, il n'y a jamais de progression vers l'avant, un élément à la fois, sur les éléments retournés par l'itérateur.

En outre, autoriser cela faciliterait également l'utilisation de la boucle for pour les énumérations, qui, comme cela a été souligné ailleurs, sont analogues aux itérateurs et non aux itérables.

Donc, ne laissez pas Iterator implémenter Iterable, mais mettez à jour la boucle for pour accepter non plus.

À votre santé,

Barney
la source
6
Je suis d'accord. Théoriquement, il pourrait y avoir confusion lors de l'obtention d'un itérateur, en utilisant une partie de celui-ci, puis en le mettant dans un foreach (rompant le contrat «chaque» de foreach), mais je ne pense pas que ce soit une raison suffisante pour ne pas avoir cette fonctionnalité.
Bart van Heukelom
Nous avons déjà mis à jour / amélioré la boucle pour accepter les tableaux au lieu de rendre les tableaux itérables. Pouvez-vous rationaliser cette décision?
Val
J'ai voté pour cette réponse et c'était une erreur. L'itérateur est avec état, si vous promouvez l'itération sur un itérateur avec la for(item : iter) {...}syntaxe, alors il provoquera une erreur lorsque le même itérateur sera itéré deux fois. Imaginez que cela Iteratorsoit passé dans la iterateOverméthode plutôt que Iterabledans cet exemple .
Ilya Silvestrov
2
Peu importe que vous utilisiez le style for (String x : strings) {...}ou while (strings.hasNext()) {...}: si vous essayez de parcourir un itérateur deux fois la deuxième fois, cela ne donnera aucun résultat, donc je ne vois pas cela en soi comme un argument contre l'autorisation de la syntaxe améliorée. La réponse de Jon est différente, car là, il montre à quel point l'enveloppement d'un Iteratordans un Iterablecauserait des problèmes, car dans ce cas, vous vous attendriez à pouvoir le réutiliser autant de fois que vous le souhaitez.
Barney
17

Comme l'ont souligné d'autres, Iteratoret ce Iterablesont deux choses différentes.

De plus, les Iteratorimplémentations sont antérieures aux boucles for améliorées.

Il est également trivial de surmonter cette limitation avec une méthode d'adaptateur simple qui ressemble à ceci lorsqu'elle est utilisée avec des importations de méthode statique:

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

Exemple d'implémentation:

  /**
   * 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();
  }

Dans Java 8, adapter un Iteratorà unIterable devient plus simple:

for (String s : (Iterable<String>) () -> iterator) {
McDowell
la source
vous avez déclaré une classe dans une fonction; est-ce que je manque quelque chose? je pensais que c'était illégal.
activedecay
Une classe peut être définie dans un bloc en Java. Ça s'appelle une classe locale
Colin D Bennett
3
Merci pour lefor (String s : (Iterable<String>) () -> iterator)
AlikElzin-kilaka
8

Comme d'autres l'ont dit, un Iterable peut être appelé plusieurs fois, renvoyant un nouvel Iterator à chaque appel; un itérateur n'est utilisé qu'une seule fois. Ils sont donc liés, mais servent des objectifs différents. Malheureusement, la méthode «compact pour» ne fonctionne qu'avec un itérable.

Ce que je décrirai ci-dessous est une façon d'avoir le meilleur des deux mondes - renvoyer un Iterable (pour une syntaxe plus agréable) même lorsque la séquence de données sous-jacente est unique.

L'astuce consiste à renvoyer une implémentation anonyme de l'itérable qui déclenche réellement le travail. Ainsi, au lieu de faire le travail qui génère une séquence ponctuelle puis de renvoyer un Iterator dessus, vous retournez un Iterable qui, chaque fois qu'il est accédé, refait le travail. Cela peut sembler un gaspillage, mais souvent vous n'appelerez l'itérable qu'une seule fois de toute façon, et même si vous l'appelez plusieurs fois, il a toujours une sémantique raisonnable (contrairement à un simple wrapper qui fait qu'un Iterator "ressemble" à un Iterable, cela a gagné ' t échouer s'il est utilisé deux fois).

Par exemple, disons que j'ai un DAO qui fournit une série d'objets à partir d'une base de données, et que je veux donner accès à cela via un itérateur (par exemple pour éviter de créer tous les objets en mémoire s'ils ne sont pas nécessaires). Maintenant, je pourrais simplement retourner un itérateur, mais cela rend l'utilisation de la valeur retournée dans une boucle moche. Donc, à la place, j'emballe tout dans un Iterable anon:

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();
            }
        };
    }
}

cela peut ensuite être utilisé dans un code comme celui-ci:

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

ce qui me permet d'utiliser la boucle for compacte tout en conservant l'utilisation de la mémoire incrémentielle.

Cette approche est «paresseuse» - le travail n'est pas fait lorsque l'itérable est demandé, mais seulement plus tard lorsque le contenu est répété - et vous devez être conscient des conséquences de cela. Dans l'exemple avec un DAO, cela signifie itérer sur les résultats dans la transaction de base de données.

Il y a donc plusieurs mises en garde, mais cela peut encore être un idiome utile dans de nombreux cas.

Andrew Cooke
la source
nice ans mais votre returning a fresh Iterator on each calldevrait être accompagné de pourquoi c'est à dire pour éviter les problèmes de concurrence ...
Anirudha
7

Incroyablement, personne d'autre n'a encore donné cette réponse. Voici comment vous pouvez "facilement" parcourir un Iteratoren utilisant la nouvelle Iterator.forEachRemaining()méthode Java 8 :

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

Bien sûr, il existe une solution "plus simple" qui fonctionne directement avec la boucle foreach, enveloppant le Iteratordans un Iterablelambda:

for (String s : (Iterable<String>) () -> it)
    System.out.println(s);
Lukas Eder
la source
5

Iteratorest une interface qui vous permet de parcourir quelque chose. C'est une implémentation de se déplacer dans une collection quelconque.

Iterable est une interface fonctionnelle qui indique que quelque chose contient un itérateur accessible.

En Java8, cela vous facilite la vie ... Si vous avez un Iteratormais que vous en avez besoin, Iterablevous pouvez simplement faire:

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

Cela fonctionne également dans la boucle for:

for (T item : ()->someIterator){
    //doSomething with item
}
Steve K
la source
2

Je suis d'accord avec la réponse acceptée, mais je veux ajouter ma propre explication.

  • L'itérateur représente l'état de la traversée, par exemple, vous pouvez obtenir l'élément courant d'un itérateur et passer au suivant.

  • Iterable représente une collection qui pourrait être parcourue, il peut renvoyer autant d'itérateurs que vous le souhaitez, chacun représentant son propre état de parcours, un itérateur peut pointer vers le premier élément, tandis qu'un autre peut pointer vers le 3ème élément.

Ce serait bien si Java for loop accepte à la fois Iterator et Iterable.

Dagang
la source
2

Pour éviter de dépendre du java.utilpackage

Selon le JSR original, une boucle for améliorée pour le langage de programmation Java ™ , les interfaces proposées:

  • java.lang.Iterable
  • java.lang.ReadOnlyIterator
    (proposé d'être modernisé java.util.Iterator, mais apparemment cela ne s'est jamais produit)

… Ont été conçus pour utiliser l' java.langespace de noms du package plutôt que java.util.

Pour citer le JSR:

Ces nouvelles interfaces servent à empêcher la dépendance du langage sur java.util qui en résulterait autrement.


À propos, l'ancien a java.util.Iterableacquis une nouvelle forEachméthode en Java 8+, à utiliser avec la syntaxe lambda (en passant a Consumer).

Voici un exemple. L' Listinterface étend l' Iterableinterface, car toute liste porte une forEachméthode.

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

J'en vois aussi beaucoup faire ceci:

public Iterator iterator() {
    return this;
}

Mais cela ne règle pas les choses! Cette méthode ne serait pas ce que vous voulez!

La méthode iterator()est censée renvoyer un nouvel itérateur à partir de zéro. Il faut donc faire quelque chose comme ça:

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 question est: y aurait-il un moyen de faire en sorte qu'une classe abstraite exécute cela? Donc, pour obtenir un IterableIterator, il suffit d'implémenter les deux méthodes next () et hasNext ()

Martin Vatshelle
la source
1

Si vous êtes venu ici à la recherche d'une solution de contournement, vous pouvez utiliser IteratorIterable . (disponible pour Java 1.6 et supérieur)

Exemple d'utilisation (inverser un vecteur).

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);
        }
    }
}

impressions

one
two
two
one
serv-inc
la source
0

Par souci de simplicité, Iterator et Iterable sont deux concepts distincts, Iterable est simplement un raccourci pour «Je peux renvoyer un Iterator». Je pense que votre code devrait être:

for(Object o : someContainer) {
}

avec someContainer instanceof SomeContainer extends Iterable<Object>

dfa
la source
0

Les itérateurs sont avec état, ils ont un élément "suivant" et deviennent "épuisés" une fois itérés. Pour voir où est le problème, exécutez le code suivant, combien de nombres sont imprimés?

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);
Roland
la source
-1

Vous pouvez essayer l'exemple suivant:

List ispresent=new ArrayList();
Iterator iterator=ispresent.iterator();
while(iterator.hasNext())
{
    System.out.println(iterator.next());
}
Sakthi King
la source