Pourquoi l'interface Iterator
ne s'étend-elle pas Iterable
?
La méthode iterator()
pourrait simplement renvoyer this
.
Est-ce délibéré ou simplement un oubli 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()) {
....
}
où listSomeObjects()
renvoie un itérateur.
Parce qu'un itérateur pointe généralement sur une seule instance dans une collection. Iterable implique que l'on puisse obtenir un itérateur d'un objet à parcourir au-dessus de ses éléments - et il n'est pas nécessaire d'itérer sur une seule instance, c'est ce que représente un itérateur.
Un itérateur est stateful. L'idée est que si vous appelez Iterable.iterator()
deux fois, vous obtenez indépendants itérateurs - pour la plupart des itérables, en tout cas. 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.
Pour ma part, je suis tout à fait d’accord pour qu’Iterator ne mette pas en oeuvre Iterable, mais je pense que la boucle for améliorée devrait accepter l’une ou l’autre. Je pense que tout l'argument "rendre les itérateurs itératifs" apparaît comme un moyen de contourner un défaut de la langue.
La raison principale de l'introduction de la boucle for améliorée était qu'elle "élimine la corvée et la prédisposition à l'erreur des itérateurs et des variables d'index lors d'une 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 tient-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 Iterables peut être réutilisé 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 qu'une progression avant sur un élément à la fois par rapport aux éléments retournés par l'itérateur.
De plus, autoriser cela faciliterait également l'utilisation de la boucle for pour les énumérations, qui, comme il a été indiqué ailleurs, sont analogues à des itérateurs et non à des itérables.
Donc, ne faites pas qu'Iterator implémente Iterable, mais mettez à jour la boucle for pour l'accepter.
À votre santé,
Comme l'ont souligné d'autres personnes, Iterator
et Iterable
sont deux choses différentes.
De plus, les implémentations de Iterator
sont antérieures aux boucles for améliorées.
Il est également facile de surmonter cette limitation avec une simple méthode d'adaptateur qui ressemble à ceci lorsqu'elle est utilisée avec des importations de méthodes statiques:
for (String line : in(lines)) {
System.out.println(line);
}
Exemple de mise en œuvre:
/**
* 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
à un Iterable
devient plus simple:
for (String s : (Iterable<String>) () -> iterator) {
Comme d'autres l'ont déjà dit, un itérateur peut être appelé plusieurs fois, renvoyant un nouvel itérateur à chaque appel; un itérateur est utilisé une seule fois. Donc, ils sont liés, mais servent des objectifs différents. Frustrement, cependant, la méthode "compact for" ne fonctionne qu'avec un itérable.
Ce que je vais décrire ci-dessous est un moyen 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 d'Iterable qui déclenche le travail. Ainsi, au lieu de faire le travail qui génère une séquence unique puis de renvoyer un itérateur par-dessus, vous renvoyez un itérable qui, chaque fois que vous y accédez, refait le travail. Cela peut sembler un gaspillage, mais souvent vous n’appelerez l’Iterable qu’une fois de toute façon, et même si vous l’appelez plusieurs fois, il possède toujours une sémantique raisonnable t échouer si 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 souhaite en fournir l'accès 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 renvoyer un itérateur, mais cela rend l’utilisation de la valeur renvoyée dans une boucle moche. Donc au lieu de cela, je range tout dans un Ionable:
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 alors être utilisé dans le code comme ceci:
class DaoUser {
private MetricDao dao;
for (Metric existing : dao.loadAll()) {
// do stuff here...
}
}
ce qui me permet d’utiliser la boucle for compact tout en conservant une utilisation incrémentielle de la mémoire.
Cette approche est "paresseuse" - le travail n'est pas terminé lorsque l'Iterable est demandé, mais seulement plus tard lorsque le contenu est itéré - et vous devez être conscient des conséquences. Dans l'exemple avec un DAO, cela signifie parcourir les résultats dans la transaction de base de données.
Il y a donc diverses mises en garde, mais cela peut quand même être un idiome utile dans de nombreux cas.
Incroyablement, personne d'autre n'a encore répondu à cette question. Voici comment vous pouvez "facilement" itérer sur un Iterator
en utilisant le nouveau Java 8 ) Iterator.forEachRemaining()
méthode:
Iterator<String> it = ...
it.forEachRemaining(System.out::println);
Bien sûr, il existe une solution "plus simple" qui fonctionne directement avec la boucle foreach, en enveloppant le Iterator
dans un Iterable
lambda:
for (String s : (Iterable<String>) () -> it)
System.out.println(s);
Iterator
est une interface qui vous permet de parcourir quelque chose. Il s’agit de passer d’une collection à une autre.
Iterable
est une interface fonctionnelle qui indique que quelque chose contient un itérateur accessible.
En Java8, cela facilite la vie ... Si vous avez un Iterator
mais avez besoin d'un Iterable
, vous pouvez simplement faire:
Iterator<T> someIterator;
Iterable<T> = ()->someIterator;
Cela fonctionne aussi dans la boucle for:
for (T item : ()->someIterator){
//doSomething with item
}
Je vois aussi beaucoup faire cela:
public Iterator iterator() {
return this;
}
Mais cela ne le rend pas juste! Cette méthode ne serait pas ce que vous voulez!
La méthode iterator()
est supposée retourner un nouvel itérateur à partir de zéro. Il faut donc faire quelque chose comme ça:
public class IterableIterator implements Iterator, Iterable {
//Constructor
IterableIterator(IterableIterator iter)
{
this.initdata = iter.initdata;
}
// methods of Iterable
public Iterator iterator() {
return new MyClass(this.somedata);
}
// methods of Iterator
public boolean hasNext() {
// ...
}
public Object next() {
// ...
}
public void remove() {
// ...
}
}
La question est: y aurait-il un moyen de faire une classe abstraite qui exécute cela? Pour obtenir un IterableIterator, il suffit d'implémenter les deux méthodes next () et hasNext ().
Si vous êtes venu ici à la recherche d'une solution de contournement, vous pouvez utiliser IteratorIterable . (disponible pour Java 1.6 et ci-dessus)
Exemple d'utilisation (inversion d'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);
}
}
}
empreintes
one
two
two
one
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 actuel d'un itérateur et passer au suivant.
Iterable représente une collection qui peut être parcourue, il peut retourner autant d'itérateurs que vous le souhaitez, chacun représentant son propre état de déplacement, 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 pour la boucle accepte à la fois Iterator et Iterable.
Les itérateurs sont stateful, ils ont un élément "next" et deviennent "épuisés" une fois itérés. Pour voir où se situe 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);
Sur une note connexe, vous pouvez trouver l’adaptateur IteratorIterable dans Apache Commons Collections4 utile. Créez simplement une instance à partir d'un itérateur, et vous obtenez l'itérable correspondant.
ID: org.Apache.commons: commons-collections4: 4.0
Par souci de simplicité, Iterator et Iterable sont deux concepts distincts. Iterable est simplement un raccourci pour "je peux retourner un itérateur". Je pense que votre code devrait être:
for(Object o : someContainer) {
}
avec someContainer instanceof SomeContainer extends Iterable<Object>
En aparté: Scala a une méthode toIterable () dans Iterator. Voir conversion implicite ou explicite scala d’itérateur en itérable