Existe-t-il un moyen d'accéder à un compteur d'itérations dans la boucle for-each de Java?


274

Existe-t-il un moyen dans la boucle for-each de Java

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

pour savoir à quelle fréquence la boucle a déjà été traitée?

Outre l'utilisation de l'ancienne et bien connue for(int i=0; i < boundary; i++)boucle, 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 pour chaque?


2
Un autre dommage est que vous ne pouvez pas utiliser la variable de boucle en dehors de la boucle,Type var = null; for (var : set) dosomething; if (var != null) then ...
Val

@Val sauf si la référence est définitive définitive. Voir ma réponse pour savoir comment utiliser cette fonctionnalité
rmuller

Réponses:


205

Non, mais vous pouvez fournir votre propre comptoir.

La raison en est que le pour-chaque boucle interne ne pas avoir un compteur; il est basé sur l' interface Iterable , c'est-à-dire qu'il utilise un Iteratorpour parcourir la "collection" - qui peut ne pas être du tout une collection, et peut en fait ne pas être du tout basé sur des index (comme une liste chaînée).


5
Ruby a une construction pour cela et Java devrait l'obtenir aussi ...for(int idx=0, String s; s : stringArray; ++idx) doSomethingWith(s, idx);
Nicholas DiPiazza

9
La réponse doit commencer par «Non, mais vous pouvez fournir votre propre compteur».
user1094206

1
Pour info @NicholasDiPiazza, il existe une each_with_indexméthode de boucle pour EnumerableRuby. Voir apidock.com/ruby/Enumerable/each_with_index
saywhatnow

@saywhatnow ouais c'est ce que je voulais dire désolé - je ne sais pas ce que je fumais quand j'ai fait le commentaire précédent
Nicholas DiPiazza

2
"La raison en est que la boucle for-each en interne n'a pas de compteur". Doit être lu comme "les développeurs Java ne se soucient pas de laisser le programmeur spécifier la variable d'index avec la valeur initiale dans la forboucle", quelque chose commefor (int i = 0; String s: stringArray; ++i)
izogfif

64

Il y a une autre manière.

Étant donné que vous écrivez votre propre Indexclasse et une méthode statique qui renvoie une Iterablesurinstance de cette classe, vous pouvez

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

Où la mise en œuvre de With.indexquelque 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++); }
                    ...
                }
            }
        }
    }
}

7
Bonne idée. J'aurais voté sans la création d'objets de courte durée de la classe Index.
mR_fr0g

3
@ mR_fr0g ne vous inquiétez pas, j'ai comparé cela et créer tous ces objets n'est pas plus lent que de réutiliser le même objet pour chaque itération. La raison en est que tous ces objets sont alloués uniquement dans l'espace Eden et jamais assez longtemps pour atteindre le tas. Leur allocation est donc aussi rapide que, par exemple, l'allocation de variables locales.
akuhn

1
@akuhn Attendez. L'espace Eden ne signifie pas qu'aucun GC n'est requis. Bien au contraire, vous devez invoquer le constructeur, scanner plus tôt avec GC et finaliser. Cela charge non seulement le processeur, mais invalide également le cache tout le temps. Rien de tel n'est nécessaire pour les variables locales. Pourquoi dites-vous que c'est "la même chose"?
Val

7
Si vous vous inquiétez des performances d'objets de courte durée de vie comme celui-ci, vous vous trompez. Les performances devraient être la DERNIÈRE chose à s'inquiéter ... la lisibilité en premier. C'est en fait une assez bonne construction.
Bill K

4
J'allais dire que je ne comprends pas vraiment la nécessité de débattre quand l'instance d'index peut être créée une fois et réutilisée / mise à jour, juste pour contester l'argument et rendre tout le monde heureux. Cependant, dans mes mesures, la version qui crée un nouvel Index () à chaque fois effectuée plus de deux fois plus vite sur ma machine, à peu près égale à un itérateur natif exécutant les mêmes itérations.
Eric Woodruff

47

La solution la plus simple consiste à exécuter simplement votre propre compteur ainsi:

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

La raison en est qu'il n'y a aucune garantie réelle que les éléments d'une collection (que cette variante d' foritère) ont même un index, ou même 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 saillante d'un ensemble.

Il existe d'autres façons de le faire sans compteur manuel, mais c'est un peu de travail pour des avantages douteux.


2
Cela semble toujours être la solution la plus claire, même avec les interfaces List et Iterable.
Joshua Pinter

27

L'utilisation de lambdas et d' interfaces fonctionnelles dans Java 8 permet de créer de nouvelles abstractions de boucle. Je peux parcourir 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

Que j'ai implémenté comme:

   @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 juste 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

Que j'ai implémenté comme:

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 commenceront à apparaître pour faire ce genre de choses, ou vous pouvez rouler les vôtres.


3
Pas une critique de la publication (c'est "la façon cool de le faire" ces jours-ci je suppose), mais j'ai du mal à voir comment c'est plus facile / meilleur qu'une simple vieille école pour loop: for (int i = 0; i < list.size (); i ++) {} Même grand-mère pourrait comprendre cela et il est probablement plus facile de taper sans la syntaxe et les caractères inhabituels utilisés par lambda. Ne vous méprenez pas, j'adore utiliser lambdas pour certaines choses (type de motif de rappel / gestionnaire d'événements) mais je ne comprends tout simplement pas l'utilisation dans des cas comme celui-ci. Excellent outil, mais en l'utilisant tout le temps , je ne peux tout simplement pas le faire.
Manius

Je suppose, un certain évitement des débordements / sous-indices d'index hors par rapport à la liste elle-même. Mais il y a l'option affichée ci-dessus: int i = 0; for (String s: stringArray) {doSomethingWith (s, i); i ++; }
Manius

21

Java 8 a introduit la méthode Iterable#forEach()/ Map#forEach(), qui est plus efficace pour de nombreuses implémentations Collection/ Mappar rapport à la boucle "classique" pour chaque boucle. Cependant, dans ce cas également, aucun index n'est fourni. L'astuce consiste à utiliser en AtomicIntegerdehors 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 d'ordinaire int.

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

16

Je crains que ce ne soit pas possible avec foreach. Mais je peux vous suggérer une simple boucle for-old style :

    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().

(Éditer)

À votre exemple changé:

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

9

L'un des changements Sunenvisagés Java7est de fournir un accès aux Iteratorboucles internes dans foreach. la syntaxe sera quelque chose comme ça (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.


7

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 que Google suggère dans la discussion sur Guava pour expliquer pourquoi ils n'ont pas fourni de CountingIterator.


4

Bien qu'il y ait tellement d'autres moyens mentionnés pour y parvenir, je partagerai mon chemin pour certains utilisateurs insatisfaits. J'utilise la fonction 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

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


(J'ai corrigé ce bogue dans la question.)
Tom Hawtin - tackline

1

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

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

3
Curieux, y a-t-il un avantage à utiliser cela par rapport à l' i=0; i++;approche apparemment plus claire ?
Joshua Pinter

1
Je suppose que si vous avez besoin d'utiliser à nouveau l'index i après doSomething dans la portée for.
gcedo

1

Pour les situations où je n'ai besoin que de l'index de temps en temps, 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);
  }
}

2
Veillez à ce que cela nécessite une implémentation .equals () des objets Arrays qui peuvent identifier un certain objet de manière unique. Ce n'est pas le cas pour les chaînes, si une certaine chaîne se trouve plusieurs fois dans le tableau, vous n'obtiendrez que l'index de la première occurrence, même si vous étiez déjà en train d'itérer sur un élément ultérieur avec la même chaîne.
Kosi2801

-1

La meilleure solution optimisée consiste à effectuer les opérations suivantes:

int i=0;

for(Type t: types) {
  ......
  i++;
}

Où Type peut être n'importe quel type de données et types est la variable sur laquelle vous appliquez pour une boucle.


Veuillez fournir votre réponse dans un format de code reproductible.
Nareman Darwish

C'est exactement la façon dont une solution alternative est recherchée. La question n'est pas sur les types mais sur les possibilités d'accéder au compteur de boucles d'une manière DIFFÉRENTE que de compter manuellement.
Kosi2801

-4

Je suis un peu surpris que personne n'ait suggéré ce qui suit (j'avoue que c'est une approche paresseuse ...); Si stringArray est une liste quelconque, vous pouvez utiliser quelque chose comme stringArray.indexOf (S) pour renvoyer une valeur pour le nombre actuel.

Remarque: cela suppose que les éléments de la liste sont uniques ou peu importe s'ils ne le sont pas (car dans ce cas, il renverra l'index de la première copie trouvée).

Il y a des situations où cela serait suffisant ...


9
Avec une performance approchant O (n²) pour toute la boucle, il est très difficile d'imaginer même quelques situations où cela serait supérieur à tout le reste. Désolé.
Kosi2801

-5

Voici un exemple de la façon dont j'ai fait cela. Cela obtient l'index au niveau de chaque boucle. J'espère que cela t'aides.

public class CheckForEachLoop {

    public static void main(String[] args) {

        String[] months = new String[] { "JANUARY", "FEBRUARY", "MARCH", "APRIL", "MAY", "JUNE", "JULY", "AUGUST",
                "SEPTEMBER", "OCTOBER", "NOVEMBER", "DECEMBER" };
        for (String s : months) {
            if (s == months[2]) { // location where you can change
              doSomethingWith(s); // however many times s and months
                                  // doSomethingWith(s) will be completed and 
                                  // added together instead of counter
            }

        }
        System.out.println(s); 


    }
}

1
Désolé, cela ne répond pas à la question. Il ne s'agit pas d'accéder au contenu mais d'un compteur à quelle fréquence la boucle a déjà été itérée.
Kosi2801

La question était de trouver l'index actuel dans la boucle dans une boucle de style pour chaque.
rumman0786
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.