web-dev-qa-db-fra.com

Pourquoi Java n'autorise-t-il pas les itérateurs (uniquement les iterables)?

Duplicate possible:
Pourquoi l'Iterator de Java n'est-il pas un Itérable?

Manière idiomatique d'utiliser pour chaque boucle étant donné un itérateur?

Peut-on utiliser pour chaque boucle pour itérer les objets de type Iterator?

Les boucles foreach sont pour autant que je sache un sucre de syntaxe ajouté dans Java 5. Donc

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 de base itérable au départ, mais seulement un itérateur (par exemple, car une classe propose deux itérateurs différents), je ne peux pas utiliser la syntaxe sugar foreach loop. Évidemment, je peux toujours faire la même itération à l'ancienne. Cependant, j'aimerais vraiment 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 est en fait un abus abominable de l'API Iterable, car il ne peut être itéré qu'une seule fois!)

S'il était conçu autour de Iterator au lieu d'iterable, 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.

Est-ce que quelqu'un sait pourquoi le langage a été conçu de cette façon? Pour éviter toute ambiguïté si une classe implémentait à la fois Iterator et 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 y a-t-il un truc de langage que je ne sais tout simplement pas?

67
Anony-Mousse

J'ai donc une explication un peu raisonnable maintenant:

Version courte: car la syntaxe s'applique également à tableaux, qui n'ont pas d'itérateurs.

Si la syntaxe était conçue autour de Iterator comme je l'ai proposé, elle serait incohérente avec les tableaux. Me laisser donner trois variantes:

A) choisi par les 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 comportement est le même et est hautement 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) autorise les tableaux et 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 maintenant incohérents. mais les tableaux et ArrayList sont très étroitement liés et devraient se comporter de la même manière. Maintenant si à un moment donné, la langue est étendue pour faire par exemple. Les tableaux implémentent Iterable, ils deviennent incohérents.

C) autorise 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 où l’un ou l’autre implémente les deuxIterable et Iterator (la boucle for est supposée obtenir un nouvel itérateur ou effectuer une itération sur le courant - se passe facilement dans des structures en forme d’arbre!?!). Un simple tie-braker ala "Iterable beats Iterator" ne le fera malheureusement pas: il introduit soudainement la différence entre le temps d'exécution et la compilation et les problèmes génériques.

Maintenant, tout à coup, nous devons faire attention à savoir si nous voulons itérer sur des collections/itérables ou des tableaux, où nous n’avons obtenu que très peu d’avantages au prix d’une grande confusion.

La manière dont "pour chaque" est écrit Java (A) est très cohérente, elle provoque très peu d'erreurs de programmation et permet un éventuel changement futur de transformation des tableaux en objets ordinaires.

Il existe une variante D) qui fonctionnerait probablement aussi sans problème: pour-chacun pour les itérateurs uniquement. De préférence, en ajoutant une méthode .iterator() à des 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 des modifications de l'environnement d'exécution, pas seulement du compilateur, et rompt la compatibilité avec les versions antérieures. De plus, la confusion mentionnée est toujours présente que

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

Une seule itération sur les données.

20
Anony-Mousse

Est-ce que quelqu'un sait pourquoi la langue a été conçue de cette façon?

Parce que for-each n'a de sens que sur les choses qui sont iter capable, et n'a pas de sens sur iter ators. Si vous avez déjà un itérateur, vous avez déjà ce qu'il vous faut pour le faire avec une simple boucle.

Comparez: je commence avec un iter capable:

// Old way
Iterator<Thingy> it = iterable.iterator();
while (it.hasNext()) {
    Thingy t = it.next();
    // Use `t`
}

// New way
for (Thingy t : iterable) {
    // Use `t`
}

Versus je commence avec un itérateur:

// Old/current way
while (iterator.hasNext()) {
    Thing t = iterator.next();
    // Use `t`
}

// Imagined way
for (Thingy t : iterator) {
   // Use `t`
}

Le deuxième exemple ne contient pas grand chose, et cela complique la sémantique du pour-chacun en créant un cas spécial.

Les questions "Pourquoi" sont toujours difficiles lorsqu'elles ne s'adressent pas aux principaux participants à la décision, mais je suppose que la complexité supplémentaire ne valait pas l'utilité marginale.


Cela dit, je pouvais voir une construction "améliorée while boucle":

while (Thingy t : iterator) {
   // Use `t`
}

... qui reprend où se trouve actuellement l'itérateur ... Meh, peut-être que cela dérouterait trop les gens. :-)

35
T.J. Crowder

L'interface Iterable a été créée exactement à cette fin (améliorée pour la boucle), comme décrit dans le JSR d'origine , bien que l'interface Iterator soit déjà utilisée.

8
assylias

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

Une fois que vous avez mis un itérateur dans une boucle "for", il ne serait plus utilisable. Je suppose que les concepteurs de langage ont décidé que cela, combiné aux cas spéciaux supplémentaires (il y en a déjà deux pour Iterable et les tableaux) dans le compilateur, permet de traduire cela en bytecode (vous ne pouvez pas réutiliser la même transformation car iterable) un détracteur de ne pas le mettre en œuvre.

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

Avec les lambdas à venir, ils pourraient rendre cela agréable et facile:

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 rupture de la compatibilité ascendante, tout en conservant une syntaxe relativement simple. Je doute qu'ils aient construit des méthodes telles que "each", "collect" et "mapper" dans les différentes interfaces, car cela annulerait la compatibilité, et vous auriez encore des tableaux à traiter.

7
Matt

Je pense qu'une partie de la réponse peut être cachée dans le fait que la boucle pour-chaque est un sucre syntaxique. Le fait est que vous voulez faire quelque chose que les gens font beaucoup, beaucoup plus facilement. Et (au moins dans mon expérience) l'idiome

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

eu tout le temps dans le code de style ancien. Et faire des choses fantaisistes avec plusieurs itérateurs n'a pas.

1
Jochen