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?
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).
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++); }
...
}
}
}
}
}
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.
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.
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
});
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);
}
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
.
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
.
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.
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));
});
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.
Il y a une "variante" à la réponse de pax ... ;-)
int i = -1;
for(String s : stringArray) {
doSomethingWith(s, ++i);
}
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
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);
}
}