86 votes

Pourquoi Java ne permet-il pas l'utilisation de foreach sur les itérateurs (uniquement sur les itérables) ?

Duplicata possible :
Pourquoi l'Iterator de Java n'est-il pas un Iterable ?

Comment utiliser la boucle for-each de manière idiomatique avec un itérateur ?

Peut-on utiliser la boucle for-each pour itérer les objets de type Iterator ?

Les boucles foreach sont, pour autant que je sache, du sucre syntaxique ajouté dans Java 5. Ainsi, les boucles foreach sont des syntaxes ajoutées à Java 5, à ma connaissance.

Iterable<O> iterable;
for(O o : iterable) {
    // Do something
}

produira essentiellement le même bytecode que

Iterable<O> iterable;
for(Iterator<O> iter = iterable.iterator(); iter.hasNext(); /* NOOP */) {
    O o = iter.next();
    // Do something
}

Cependant, si je n'ai pas d'itérable en premier lieu, mais seulement un itérateur (par exemple, parce qu'une classe offre deux itérateurs différents), je ne peux pas utiliser la boucle foreach de la syntaxe du sucre. Évidemment, je peux toujours faire l'itération à l'ancienne. Cependant, j'aimerais en fait faire :

Iterator<O> iter;
for(O o : iter /* Iterator<O>, not Iterable<O>! */) {
     // Do something
}

Et bien sûr, je peux faire un faux Iterable :

class Adapter<O> implements Iterable<O> {
    Iterator<O> iter;

    public Adapter(Iterator<O> iter) {
        this.iter = iter;
    }

    @Override
    public Iterator<O> iterator() {
        return iter;
    }
}

(Ce qui, en fait, est une utilisation abusive de l'API Iterable, puisqu'il ne peut être itéré qu'une seule fois).

S'il était conçu autour de Iterator au lieu de itérable, on pourrait faire un certain nombre de choses intéressantes :

for(O o : iterable.iterator()) {} // Iterate over Iterable and Collections

for(O o : list.backwardsIterator()) {} // Or backwards

Iterator<O> iter;
for(O o : iter) {
    if (o.something()) { iter.remove(); }
    if (o.something()) { break; }
}
for(O : iter) { } // Do something with the remaining elements only.

Quelqu'un sait-il pourquoi la langue a été conçue de cette façon ? Pour éviter toute ambiguïté si une classe implémentait à la fois Iterator y Iterable ? Pour éviter les erreurs de programmation qui supposent que "for(O o : iter)" traitera tous les éléments deux fois (et oubliera d'obtenir un nouvel itérateur) ? Ou y a-t-il une autre raison à cela ?

Ou existe-t-il une astuce linguistique que je ne connais pas ?

24voto

Anony-Mousse Points 24646

J'ai donc une explication quelque peu raisonnable maintenant :

Version courte : Comme la syntaxe s'applique également aux tableaux qui n'ont pas d'itérateur.

Si la syntaxe était conçue autour de Iterator comme je l'ai proposé, il serait incompatible avec les tableaux. Permettez-moi de vous présenter trois variantes :

A) tel qu'il a été choisi par les développeurs de Java :

Object[] array;
for(Object o : array) { }
Iterable<Object> list;
for(Object o : list) { }
Iterator<Object> iter;
while(iter.hasNext()) { Object o = iter.next(); }

Le se comporte de la même manière et est très cohérent dans les tableaux et les collections. Les itérateurs doivent toutefois utiliser le style d'itération classique (qui, au moins, n'est pas susceptible de provoquer des erreurs).

B) autorisent les tableaux et les Iterators :

Object[] array;
for(Object o : array) { }
Iterable<Object> list;
for(Object o : list.iterator()) { }
Iterator<Object> iter;
for(Object o : iter) { }

Les tableaux et les collections sont incompatibles, mais les tableaux et les listes de tableaux sont étroitement liés et devraient se comporter de la même manière. Si, à un moment donné, le langage est étendu pour mettre en œuvre, par exemple, des tableaux Iterable il devient incohérent.

C) autoriser les trois :

Object[] array;
for(Object o : array) { }
Iterable<Object> list;
for(Object o : list) { }
Iterator<Object> iter;
for(Object o : iter) { }

Maintenant, si nous nous retrouvons dans des situations peu claires lorsque quelqu'un met en œuvre des à la fois Iterable y Iterator (la boucle for est-elle censée obtenir un nouvel itérateur ou itérer sur l'itérateur actuel - ce qui se produit facilement dans les structures arborescentes !?!). Une simple rupture d'égalité du type "Iterable bat Iterator" ne suffit malheureusement pas : elle introduit soudainement une différence entre le temps d'exécution et le temps de compilation, ainsi que des problèmes de généricité.

Tout à coup, nous devons faire attention à savoir si nous voulons itérer sur des collections/ tablettes ou sur des tableaux, ce qui nous apporte très peu d'avantages au prix d'une grande confusion.

La méthode "for each" utilisée en Java (A) est très cohérente, elle ne provoque que très peu d'erreurs de programmation et elle permet de transformer les tableaux en objets normaux, ce qui pourrait se faire à l'avenir.

Il existe une variante D) Cela fonctionnerait probablement aussi bien : for-each pour les itérateurs uniquement. De préférence en ajoutant un .iterator() aux tableaux primitifs :

Object[] array;
for(Object o : array.iterator()) { }
Iterable<Object> list;
for(Object o : list.iterator()) { }
Iterator<Object> iter;
for(Object o : iter) { }

Mais cela nécessite de modifier l'environnement d'exécution, et pas seulement le compilateur, et rompt la compatibilité ascendante. De plus, la confusion mentionnée est toujours présente, à savoir que

Iterator<Object> iter;
for(Object o : iter) { }
for(Object o : iter) { }

N'itère qu'une seule fois sur les données.

14voto

assylias Points 102015

L'interface Iterable a été créée exactement dans ce but (boucle for améliorée), comme décrit dans le document JSR original bien que l'interface Iterator ait déjà été utilisée.

En ce qui concerne les nouvelles interfaces discutées dans le JSR (notez les noms des paquets) :

  • java.lang.Iterable
  • java.lang.ReadOnlyIterator (proposé dans le JSR pour être intégré dans le système de gestion de l'information) java.util.Iterator bien que cela n'ait pas été fait)

le JSR dit :

Ces nouvelles interfaces permettent d'éviter la dépendance du langage à l'égard des java.util qui en résulterait autrement.

10voto

Matt Points 5441

Parce que la boucle "for" serait destructive pour l'itérateur. L'itérateur ne peut pas être réinitialisé (c'est-à-dire ramené au début) à moins qu'il n'implémente la sous-interface ListIterator.

Une fois qu'un itérateur est passé par une boucle "for", il n'est plus utilisable. Je pense que les concepteurs du langage ont décidé que cela, combiné avec les cas spéciaux supplémentaires (il y en a déjà deux pour les Iterable et les tableaux) dans le compilateur pour traduire cela en bytecode (on ne pouvait pas réutiliser la même transformation que pour les iterable) était un détracteur suffisant pour ne pas l'implémenter.

Lorsque vous faites cela vous-même dans le code via l'interface de l'itérateur, il serait au moins apparemment évident de savoir ce qui se passe.

Avec l'arrivée des lambdas, cela pourrait être facile et agréable :

Iterator<String> iterator = ...;
Collections.each ( iterator, (String s) => { System.out.println(s); } );

List<String> list = ...;
Collections.each ( list, (String s) => { System.out.println(s); } );

sans rompre la compatibilité ascendante et en conservant une syntaxe relativement simple. Je doute qu'ils aient intégré des méthodes telles que "each", "collect" et "map" dans les différentes interfaces, car cela romprait la compatibilité ascendante, sans compter qu'il faudrait toujours gérer les tableaux.

1voto

Jochen Points 1982

Je pense qu'une partie de la réponse peut être cachée dans le fait que la boucle for-each est du sucre syntaxique. Le fait est que vous voulez rendre plus facile quelque chose que les gens font beaucoup. Et (du moins d'après mon expérience) l'idiome

Iterator iterator = iterable.iterator();
while( iterator.hasNext() ) {
  Element e = (Element)iterator.next() ;
}

se produisait tout le temps dans le code de l'ancien temps. Et faire des choses fantaisistes avec des itérateurs multiples ne l'était pas.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X