web-dev-qa-db-fra.com

Existe-t-il un moyen d'accéder à un compteur d'itérations en Java pour chaque boucle?

Existe-t-il un moyen dans la boucle Java pour chaque boucle?

for(String s : stringArray) {
  doSomethingWith(s);
}

savoir combien de fois la boucle a déjà été traitée?

En plus d’utiliser l’ancienne et bien connue for(int i=0; i < boundary; i++) - loop, la construction

int i = 0;
for(String s : stringArray) {
  doSomethingWith(s);
  i++;
}

la seule façon d'avoir un tel compteur disponible dans une boucle for-each?

253
Kosi2801

Non, mais vous pouvez fournir votre propre compteur.

La raison en est que la boucle for-each en interne ne possède pas un compteur; il est basé sur l'interface Iterable , c'est-à-dire qu'il utilise un Iterator pour parcourir la "collection" - ce qui peut ne pas être une collection du tout, et peut-être même être quelque chose de pas du tout basé sur des index (comme une liste chaînée).

184
Michael Borgwardt

Il y a un autre moyen.

Étant donné que vous écrivez votre propre classe Index et une méthode statique qui retourne un Iterable sur des occurrences de cette classe, vous pouvez:

for (Index<String> each: With.index(stringArray)) {
    each.value;
    each.index;
    ...
}

Où l’implémentation de With.index ressemble à quelque chose comme

class With {
    public static <T> Iterable<Index<T>> index(final T[] array) {
        return new Iterable<Index<T>>() {
            public Iterator<Index<T>> iterator() {
                return new Iterator<Index<T>>() {
                    index = 0;
                    public boolean hasNext() { return index < array.size }
                    public Index<T> next() { return new Index(array[index], index++); }
                    ...
                }
            }
        }
    }
}
60
akuhn

La solution la plus simple est simplement lancer votre propre compteur:

int i = 0;
for (String s : stringArray) {
    doSomethingWith(s, i);
    i++;
}

La raison en est qu’il n’ya aucune garantie réelle que les éléments d’une collection (que cette variante de for itère sur) soient même ont un index, ou même aient un ordre défini (certaines collections peuvent changer l’ordre lorsque vous ajoutez ou supprimez des éléments).

Voir par exemple le code suivant:

import Java.util.*;

public class TestApp {
  public static void AddAndDump(AbstractSet<String> set, String str) {
    System.out.println("Adding [" + str + "]");
    set.add(str);
    int i = 0;
    for(String s : set) {
        System.out.println("   " + i + ": " + s);
        i++;
    }
  }

  public static void main(String[] args) {
    AbstractSet<String> coll = new HashSet<String>();
    AddAndDump(coll, "Hello");
    AddAndDump(coll, "My");
    AddAndDump(coll, "Name");
    AddAndDump(coll, "Is");
    AddAndDump(coll, "Pax");
  }
}

Lorsque vous exécutez cela, vous pouvez voir quelque chose comme:

Adding [Hello]
   0: Hello
Adding [My]
   0: Hello
   1: My
Adding [Name]
   0: Hello
   1: My
   2: Name
Adding [Is]
   0: Hello
   1: Is
   2: My
   3: Name
Adding [Pax]
   0: Hello
   1: Pax
   2: Is
   3: My
   4: Name

indiquant que, à juste titre, l'ordre n'est pas considéré comme une caractéristique essentielle d'un ensemble.

Il y a d'autres façons de le faire sans un compteur manuel, mais cela demande beaucoup de travail pour un bénéfice douteux.

41
paxdiablo

Utiliser lambdas et interfaces fonctionnelles in Java 8 permet de créer de nouvelles abstractions de boucle. Je peux passer en boucle sur une collection avec l'index et la taille de la collection:

List<String> strings = Arrays.asList("one", "two","three","four");
forEach(strings, (x, i, n) -> System.out.println("" + (i+1) + "/"+n+": " + x));

Quelles sorties:

1/4: one
2/4: two
3/4: three
4/4: four

Ce que j'ai implémenté en tant que:

   @FunctionalInterface
   public interface LoopWithIndexAndSizeConsumer<T> {
       void accept(T t, int i, int n);
   }
   public static <T> void forEach(Collection<T> collection,
                                  LoopWithIndexAndSizeConsumer<T> consumer) {
      int index = 0;
      for (T object : collection){
         consumer.accept(object, index++, collection.size());
      }
   }

Les possibilités sont infinies. Par exemple, je crée une abstraction qui utilise une fonction spéciale uniquement pour le premier élément:

forEachHeadTail(strings, 
                (head) -> System.out.print(head), 
                (tail) -> System.out.print(","+tail));

Qui imprime correctement une liste séparée par des virgules:

one,two,three,four

Ce que j'ai implémenté en tant que:

public static <T> void forEachHeadTail(Collection<T> collection, 
                                       Consumer<T> headFunc, 
                                       Consumer<T> tailFunc) {
   int index = 0;
   for (T object : collection){
      if (index++ == 0){
         headFunc.accept(object);
      }
      else{
         tailFunc.accept(object);
      }
   }
}

Les bibliothèques vont commencer à apparaître pour faire ce genre de choses, ou vous pouvez rouler vous-même.

25
James Scriven

Java 8 a introduit la méthode Iterable#forEach()/Map#forEach(), qui est plus efficace pour de nombreuses implémentations Collection/Map par rapport à la boucle "classique" de chaque boucle. Cependant, dans ce cas également, aucun index n'est fourni. Le truc ici consiste à utiliser AtomicInteger en dehors de l'expression lambda. Remarque: les variables utilisées dans l'expression lambda doivent être effectivement finales, c'est pourquoi nous ne pouvons pas utiliser un int ordinaire.

final AtomicInteger indexHolder = new AtomicInteger();
map.forEach((k, v) -> {
    final int index = indexHolder.getAndIncrement();
    // use the index
});
16
rmuller

J'ai bien peur que ce ne soit pas possible avec foreach. Mais je peux vous suggérer un simple style ancien pour les boucles :

    List<String> l = new ArrayList<String>();

    l.add("a");
    l.add("b");
    l.add("c");
    l.add("d");

    // the array
    String[] array = new String[l.size()];

    for(ListIterator<String> it =l.listIterator(); it.hasNext() ;)
    {
        array[it.nextIndex()] = it.next();
    }

Notez que l'interface List vous donne accès à it.nextIndex().

(edit)

Pour votre exemple changé:

    for(ListIterator<String> it =l.listIterator(); it.hasNext() ;)
    {
        int i = it.nextIndex();
        doSomethingWith(it.next(), i);
    }
16
bruno conde

Un des changements que Sun envisage pour Java7 consiste à fournir un accès aux boucles Iterator in foreach intérieures. la syntaxe ressemblera à ceci (si cela est accepté):

for (String str : list : it) {
  if (str.length() > 100) {
    it.remove();
  }
}

C'est du sucre syntaxique, mais apparemment, beaucoup de demandes ont été faites pour cette fonctionnalité. Mais jusqu'à ce qu'il soit approuvé, vous devrez compter les itérations vous-même ou utiliser une boucle for régulière avec un Iterator.

9
Yuval

Solution idiomatique:

final Set<Double> doubles; // boilerplate
final Iterator<Double> iterator = doubles.iterator();
for (int ordinal = 0; iterator.hasNext(); ordinal++)
{
    System.out.printf("%d:%f",ordinal,iterator.next());
    System.out.println();
}

c’est en fait la solution suggérée par Google dans le discussion sur le goyav sur la raison pour laquelle ils n’ont pas fourni de CountingIterator.

5
user177800

Si vous avez besoin d'un compteur dans une boucle pour chaque boucle, vous devez vous compter. Il n'y a pas de compteur intégré pour autant que je sache.

2
EricSchaefer

Bien que de nombreux autres moyens soient mentionnés pour atteindre le même objectif, je partagerai mon chemin avec certains utilisateurs insatisfaits. J'utilise la fonctionnalité Java 8 IntStream.

1. Tableaux

Object[] obj = {1,2,3,4,5,6,7};
IntStream.range(0, obj.length).forEach(index-> {
    System.out.println("index: " + index);
    System.out.println("value: " + obj[index]);
});

2. Liste

List<String> strings = new ArrayList<String>();
Collections.addAll(strings,"A","B","C","D");

IntStream.range(0, strings.size()).forEach(index-> {
    System.out.println("index: " + index);
    System.out.println("value: " + strings.get(index));
});
2
Amar Prakash Pandey
int i = 0;
for(String s : stringArray) {
    doSomethingWith(s,i);
    ++i;
} 

Il y a beaucoup de façons mais la méthode en est une.

1
Sankar Karthi

Il y a une "variante" à la réponse de pax ... ;-)

int i = -1;
for(String s : stringArray) {
    doSomethingWith(s, ++i);
}
1
Jörg

J'ai constaté que l'utilisation d'une boucle for simple avec un index incrémenté était la solution la plus efficace et la plus fiable, comme indiqué ici: https://stackoverflow.com/a/3431543/2430549

0
HoldOffHunger

Dans les cas où je n'ai besoin que occasionnellement de l'index, comme dans une clause catch, j'utilise parfois indexOf.

for(String s : stringArray) {
  try {
    doSomethingWith(s);
  } catch (Exception e) {
    LOGGER.warn("Had some kind of problem with string " +
      stringArray.indexOf(s) + ": " + s, e);
  }
}
0
Jeremy