Gibt es eine Möglichkeit, auf einen Iterationszähler in Javas for-each-Schleife zuzugreifen?

273

Gibt es einen Weg in Javas for-each-Schleife?

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

um herauszufinden, wie oft die Schleife bereits verarbeitet wurde?

Abgesehen von der Verwendung der alten und bekannten for(int i=0; i < boundary; i++)- Schleife ist das Konstrukt

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

der einzige Weg, einen solchen Zähler in einer for-each-Schleife verfügbar zu haben?

Kosi2801
quelle
2
Ein weiteres Schade ist, dass Sie die Schleifenvariable nicht außerhalb der Schleife verwenden können,Type var = null; for (var : set) dosomething; if (var != null) then ...
Val
@Val, es sei denn, die Referenz ist endgültig. Siehe meine Antwort für die Verwendung dieser Funktion
rmuller

Antworten:

205

Nein, aber Sie können Ihren eigenen Zähler bereitstellen.

Der Grund dafür ist , dass die für jede Schleife-intern nicht den Fall hat einen Zähler; Es basiert auf der Iterable- Schnittstelle, dh es verwendet eine IteratorSchleife durch die "Sammlung" - die möglicherweise überhaupt keine Sammlung ist und tatsächlich überhaupt nicht auf Indizes basiert (z. B. eine verknüpfte Liste).

Michael Borgwardt
quelle
5
Ruby hat ein Konstrukt dafür und Java sollte es auch bekommen ...for(int idx=0, String s; s : stringArray; ++idx) doSomethingWith(s, idx);
Nicholas DiPiazza
9
Die Antwort sollte mit "Nein, aber Sie können Ihren eigenen Zähler bereitstellen." Beginnen.
user1094206
1
Zu Ihrer Information @NicholasDiPiazza gibt es eine each_with_indexSchleifenmethode für Enumerablein Ruby. Siehe apidock.com/ruby/Enumerable/each_with_index
saywhatnow
@saywhatnow ja, das habe ich gemeint, tut mir leid - ich bin mir nicht sicher, was ich geraucht habe, als ich den vorherigen Kommentar abgegeben habe
Nicholas DiPiazza
2
"Der Grund dafür ist, dass die for-each-Schleife intern keinen Zähler hat." Sollte gelesen werden als "Java-Entwickler wollten nicht, dass Programmierer forfor (int i = 0; String s: stringArray; ++i)
Indexvariable
64

Es geht auch anders.

Vorausgesetzt, Sie schreiben Ihre eigene IndexKlasse und eine statische Methode, die eine IterableÜberinstanz dieser Klasse zurückgibt , können Sie

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

Wo die Implementierung von With.indexso etwas ist

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++); }
                    ...
                }
            }
        }
    }
}
akuhn
quelle
7
Gute Idee. Ohne die kurzlebige Objekterstellung der Index-Klasse hätte ich positiv gestimmt.
mR_fr0g
3
@ mR_fr0g Keine Sorge, ich habe dies verglichen und das Erstellen all dieser Objekte ist nicht langsamer als die Wiederverwendung des gleichen Objekts für jede Iteration. Der Grund dafür ist, dass all diese Objekte nur im Eden-Raum zugewiesen werden und niemals lange genug leben, um den Haufen zu erreichen. Das Zuweisen ist also so schnell wie das Zuweisen lokaler Variablen.
Akuhn
1
@akuhn Warte. Eden Space bedeutet nicht, dass kein GC erneut benötigt wird. Im Gegenteil, Sie müssen den Konstruktor aufrufen, früher mit GC scannen und finalisieren. Dies lädt nicht nur die CPU, sondern macht auch den Cache ständig ungültig. Für lokale Variablen ist nichts dergleichen erforderlich. Warum sagst du, dass dies "das gleiche" ist?
Val
7
Wenn Sie sich Sorgen über die Leistung von kurzlebigen Objekten wie diesem machen, machen Sie es falsch. Leistung sollte das LETZTE sein, worüber man sich Sorgen machen muss ... Lesbarkeit zuerst. Dies ist eigentlich ein ziemlich gutes Konstrukt.
Bill K
4
Ich wollte sagen, dass ich die Notwendigkeit einer Debatte nicht wirklich verstehe, wenn die Indexinstanz einmal erstellt und wiederverwendet / aktualisiert werden kann, nur um das Argument zu diskutieren und alle glücklich zu machen. Bei meinen Messungen wurde die Version, die jedes Mal einen neuen Index () erstellt, auf meinem Computer mehr als doppelt so schnell ausgeführt, was ungefähr einem nativen Iterator entspricht, der dieselben Iterationen ausführt.
Eric Woodruff
47

Die einfachste Lösung besteht darin , einfach einen eigenen Zähler zu betreiben:

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

Der Grund dafür ist , weil es gibt keine tatsächliche Garantie , dass Elemente in einer Auflistung (die die Variante foriteriert) selbst hat einen Index oder sogar einen hat definierten Reihenfolge (einige Sammlungen können die Reihenfolge ändern , wenn Sie hinzufügen oder entfernen Elemente).

Siehe zum Beispiel den folgenden Code:

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");
  }
}

Wenn Sie das ausführen, können Sie Folgendes sehen:

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

Dies zeigt zu Recht an, dass die Reihenfolge nicht als herausragendes Merkmal einer Menge angesehen wird.

Es gibt andere Möglichkeiten, dies ohne manuellen Zähler zu tun, aber es ist ein gutes Stück Arbeit zum zweifelhaften Vorteil.

paxdiablo
quelle
2
Dies scheint immer noch die klarste Lösung zu sein, selbst mit List- und Iterable-Schnittstellen.
Joshua Pinter
27

Die Verwendung von Lambdas und funktionalen Schnittstellen in Java 8 ermöglicht das Erstellen neuer Schleifenabstraktionen. Ich kann eine Sammlung mit dem Index und der Sammlungsgröße durchlaufen:

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

Welche Ausgänge:

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

Was ich implementiert habe als:

   @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());
      }
   }

Die Möglichkeiten sind endlos. Zum Beispiel erstelle ich eine Abstraktion, die eine spezielle Funktion nur für das erste Element verwendet:

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

Welches druckt eine durch Kommas getrennte Liste korrekt:

one,two,three,four

Was ich implementiert habe als:

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);
      }
   }
}

Bibliotheken werden angezeigt, um diese Art von Dingen zu erledigen, oder Sie können Ihre eigenen rollen.

James Scriven
quelle
3
Keine Kritik an dem Beitrag (es ist heutzutage "die coole Art, es zu tun", denke ich), aber ich habe Mühe zu sehen, wie dies einfacher / besser ist als eine einfache alte Schule für Schleife: für (int i = 0; i < list.size (); i ++) {} Sogar Oma könnte dies verstehen und es ist wahrscheinlich einfacher, ohne die Syntax und ungewöhnlichen Zeichen, die ein Lambda verwendet, zu tippen. Versteh mich nicht falsch, ich liebe es, Lambdas für bestimmte Dinge zu verwenden (Rückruf- / Ereignishandler-Muster), aber ich kann die Verwendung in solchen Fällen einfach nicht verstehen. Tolles Tool, aber ich kann es die ganze Zeit nicht benutzen.
Manius
Ich nehme an, eine gewisse Vermeidung von Off-by-One-Indexüber- / -unterläufen gegenüber der Liste selbst. Es gibt jedoch die oben angegebene Option: int i = 0; for (String s: stringArray) {doSomethingWith (s, i); i ++; }
Manius
21

Java 8 führte die Iterable#forEach()/ Map#forEach()-Methode ein, die für viele Collection/ MapImplementierungen effizienter ist als die "klassische" for-each-Schleife. Auch in diesem Fall wird jedoch kein Index bereitgestellt. Der Trick hier ist, AtomicIntegeraußerhalb des Lambda-Ausdrucks zu verwenden. Hinweis: Variablen, die im Lambda-Ausdruck verwendet werden, müssen effektiv endgültig sein. Deshalb können wir keine gewöhnlichen Variablen verwenden int.

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

Ich fürchte, das ist mit nicht möglich foreach. Aber ich kann Ihnen einen einfachen For-Loop im alten Stil vorschlagen :

    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();
    }

Beachten Sie, dass Sie über die List- Oberfläche Zugriff haben it.nextIndex().

(bearbeiten)

Zu Ihrem geänderten Beispiel:

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

Eine der Änderungen, die Sunin Betracht gezogen werden, Java7besteht darin, den Zugang zum Inneren Iteratorin foreach-Schleifen zu ermöglichen. Die Syntax lautet ungefähr so ​​(wenn dies akzeptiert wird):

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

Dies ist syntaktischer Zucker, aber anscheinend wurden viele Anfragen für diese Funktion gestellt. Aber bis es genehmigt ist, müssen Sie die Iterationen selbst zählen oder eine reguläre for-Schleife mit einem verwenden Iterator.

Yuval
quelle
7

Idiomatische Lösung:

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();
}

Dies ist eigentlich die Lösung, die Google in der Guava-Diskussion vorschlägt, warum sie keine bereitgestellt haben CountingIterator.


quelle
4

Obwohl es so viele andere Möglichkeiten gibt, dasselbe zu erreichen, werde ich meinen Weg für einige unzufriedene Benutzer teilen. Ich verwende die Java 8 IntStream-Funktion.

1. Arrays

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));
});
Amar Prakash Pandey
quelle
2

Wenn Sie einen Zähler in einer für jede Schleife benötigen, müssen Sie sich selbst zählen. Soweit ich weiß, gibt es keinen eingebauten Schalter.

EricSchaefer
quelle
(Ich habe diesen Fehler in der Frage behoben.)
Tom Hawtin - Tackline
1

Es gibt eine "Variante" zu pax 'Antwort ... ;-)

int i = -1;
for(String s : stringArray) {
    doSomethingWith(s, ++i);
}
Jörg
quelle
3
Neugierig, gibt es einen Vorteil gegenüber dem scheinbar klareren i=0; i++;Ansatz?
Joshua Pinter
1
Ich denke, wenn Sie den i-Index nach doSomething im for-Bereich erneut verwenden müssen.
Gcedo
1

In Situationen, in denen ich den Index nur gelegentlich benötige, wie in einer catch-Klausel, verwende ich manchmal 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);
  }
}
Jeremy
quelle
2
Stellen Sie sicher, dass hierfür eine .equals () - Implementierung der Arrays-Objekte erforderlich ist, mit der ein bestimmtes Objekt eindeutig identifiziert werden kann. Dies ist bei Zeichenfolgen nicht der Fall. Wenn sich eine bestimmte Zeichenfolge mehrmals im Array befindet, erhalten Sie nur den Index des ersten Vorkommens, selbst wenn Sie bereits ein späteres Element mit derselben Zeichenfolge durchlaufen haben.
Kosi2801
-1

Die beste und optimierte Lösung besteht darin, Folgendes zu tun:

int i=0;

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

Wobei Typ ein beliebiger Datentyp sein kann und Typ ist die Variable, auf die Sie eine Schleife anwenden.

Abhijeet Chopra
quelle
Bitte geben Sie Ihre Antwort in einem reproduzierbaren Codeformat an.
Nareman Darwish
Genau auf diese Weise wird eine alternative Lösung gewünscht. Die Frage betrifft nicht Typen, sondern Möglichkeiten, auf den Schleifenzähler auf eine andere Weise zuzugreifen als manuell zu zählen.
Kosi2801
-4

Ich bin ein wenig überrascht, dass niemand Folgendes vorgeschlagen hat (ich gebe zu, es ist ein fauler Ansatz ...); Wenn stringArray eine Liste ist, können Sie stringArray.indexOf (S) verwenden, um einen Wert für die aktuelle Anzahl zurückzugeben.

Hinweis: Dies setzt voraus, dass die Elemente der Liste eindeutig sind oder dass es keine Rolle spielt, ob sie nicht eindeutig sind (da in diesem Fall der Index der ersten gefundenen Kopie zurückgegeben wird).

Es gibt Situationen, in denen das ausreichen würde ...

Rik
quelle
9
Bei einer Leistung, die sich O (n²) für die gesamte Schleife nähert, ist es sehr schwer, sich selbst einige Situationen vorzustellen, in denen dies allem anderen überlegen wäre. Es tut uns leid.
Kosi2801
-5

Hier ist ein Beispiel, wie ich das gemacht habe. Dies erhält den Index für jede Schleife. Hoffe das hilft.

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); 


    }
}
ashkanaral
quelle
1
Entschuldigung, dies beantwortet die Frage nicht. Es geht nicht um den Zugriff auf den Inhalt, sondern um einen Zähler, wie oft die Schleife bereits iteriert wurde.
Kosi2801
Die Frage war, den aktuellen Index in der Schleife in einer Schleife für jeden Stil herauszufinden.
rumman0786