Y a-t-il un moyen d'accéder à un compteur d'itérations dans Java pour chaque boucle?
Est-il possible en Java de la boucle for-each
for(String s : stringArray) {
doSomethingWith(s);
}
pour savoir combien de fois la boucle a déjà été traité?
mis à part l'utilisation de l'ancien et bien connu for(int i=0; i < boundary; i++)
- boucle, est la construction
int i = 0;
for(String s : stringArray) {
doSomethingWith(s);
i++;
}
la seule façon d'avoir un compteur disponible dans une boucle for-each?
14 réponses
Non, mais vous pouvez fournir votre propre compteur.
la raison en est que la boucle pour-chaque en interne ne a un compteur; il est basé sur le itérable interface, c. - à-d. il utilise un Iterator
pour boucle à travers la" collection " - qui peut ne pas être une collection du tout, et peut en fait être quelque chose pas du tout basé sur des index (comme une liste liée).
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 les instances de cette classe vous pouvez
for (Index<String> each: With.index(stringArray)) {
each.value;
each.index;
...
}
où la mise en œuvre de With.index
est 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++); }
...
}
}
}
}
}
la solution la plus facile est pour juste exécuter votre propre compteur ainsi:
int i = 0;
for (String s : stringArray) {
doSomethingWith(s, i);
i++;
}
la raison en est qu'il n'y a pas de garantie réelle que les articles dans une collection (que cette variante de for
itère plus) même ont un index, ou même avoir un ordre défini (certaines collections peuvent changer l'ordre quand 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");
}
}
quand 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 de l'ensemble.
il y a d'autres façons de le faire sans un compteur manuel, mais c'est un peu de travail pour un bénéfice douteux.
utilisant lambdas et interfaces fonctionnelles dans Java 8 rend la création de nouvelles abstractions de boucle possible. Je peux boucler 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));
qui produit:
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 votre propre.
je crains que ce ne soit pas possible avec foreach
. Mais je peux vous suggérer un simple vieux-style pour-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, la liste interface vous donne accès à it.nextIndex()
.
(edit)
à votre exemple modifié:
for(ListIterator<String> it =l.listIterator(); it.hasNext() ;)
{
int i = it.nextIndex();
doSomethingWith(it.next(), i);
}
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 méthode" classique " pour chaque boucle. Toutefois, dans ce cas également un index n'est pas fourni. Le truc ici est d'utiliser AtomicInteger
en dehors de l'expression lambda. Note: 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
});
un des changements Sun
est considéré pour Java7
est de fournir l'accès à l'intérieur Iterator
dans les boucles de foreach. la syntaxe sera quelque chose comme ceci (si c'est accepté):
for (String str : list : it) {
if (str.length() > 100) {
it.remove();
}
}
C'est sucre syntaxique, mais apparemment beaucoup de demandes ont été faites pour cette fonction. Mais jusqu'à ce qu'il soit approuvé, vous devrez compter les itérations vous-même, ou utiliser un régulier pour boucle avec un Iterator
.
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 la goyave sur la raison pour laquelle ils n'ont pas fourni un
CountingIterator
.
si vous avez besoin d'un compteur dans une boucle pour chaque boucle, vous devez vous compter. Il n'y a pas de comptoir construit à ma connaissance.
il y a une" variante " à la réponse de pax... ;- )
int i = -1;
for(String s : stringArray) {
doSomethingWith(s, ++i);
}
int i = 0;
for(String s : stringArray) {
doSomethingWith(s,i);
++i;
}
il y a plusieurs façons mais la méthode est l'une des façons
j'ai trouvé que l'utilisation d'un simple pour boucle avec un indice d'incrémentation était la solution la plus efficace et fiable, comme posté ici: https://stackoverflow.com/a/3431543/2430549
pour les situations où je n'ai besoin de l'index qu'occasionnellement, comme dans une clause de catch, j'utiliserai 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);
}
}
je suis un peu surpris que personne n'ait suggéré ce qui suit (je reconnais que c'est une approche paresseuse...); Si stringArray est une liste, vous pourriez utiliser quelque chose comme stringArray.indexOf (S) pour retourner une valeur pour le compte courant.
Note: ceci suppose que les éléments de la liste sont uniques, ou qu'il n'importe pas s'ils ne sont pas uniques (parce que dans ce cas, il retournera l'index de la première copie trouvée).
il y a des situations qui qui serait suffisant...