C'è un modo per accedere a un contatore di iterazioni nel ciclo for-each di Java?


274

C'è un modo nel ciclo for-each di Java

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

per scoprire con quale frequenza il loop è già stato elaborato?

Oltre a usare il vecchio e noto for(int i=0; i < boundary; i++)- loop, è il costrutto

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

l'unico modo per avere un tale contatore disponibile in un ciclo per ogni?


2
Un altro peccato è che non è possibile utilizzare la variabile loop al di fuori del loop,Type var = null; for (var : set) dosomething; if (var != null) then ...
Val

@Val a meno che il riferimento sia effettivo finale. Vedi la mia risposta su come utilizzare questa funzione
rmuller,

Risposte:


205

No, ma puoi fornire il tuo contatore.

La ragione di ciò è che il ciclo for-each internamente non ha un contatore; si basa sull'interfaccia Iterable , ovvero utilizza un Iteratorciclo per la "raccolta", che potrebbe non essere affatto una raccolta e potrebbe in effetti essere qualcosa che non si basa affatto su indici (come un elenco collegato).


5
Ruby ha un costrutto per questo e anche Java dovrebbe prenderlo ...for(int idx=0, String s; s : stringArray; ++idx) doSomethingWith(s, idx);
Nicholas DiPiazza

9
La risposta dovrebbe iniziare con "No, ma puoi fornire il tuo contatore".
user1094206,

1
Cordiali saluti @NicholasDiPiazza c'è un each_with_indexmetodo loop per Enumerablein Ruby. Vedi apidock.com/ruby/Enumerable/each_with_index
saywhatnow il

@saywhatnow sì, questo è quello che volevo dire scusa - non sono sicuro di cosa stavo fumando quando ho fatto il commento precedente
Nicholas DiPiazza il

2
"La ragione di ciò è che il ciclo for-each internamente non ha un contatore". Dovrebbe essere letto come "Gli sviluppatori Java non si sono preoccupati di lasciare che il programmatore specificasse la variabile di indice con il valore iniziale all'interno del forciclo", qualcosa del tipofor (int i = 0; String s: stringArray; ++i)
izogfif

64

C'è un altro modo.

Dato che scrivi la tua Indexclasse e un metodo statico che restituisce un Iterableover di istanze di questa classe, puoi farlo

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

Dove l'implementazione di With.indexè qualcosa di simile

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
Bella idea. Avrei votato se non fosse stato per la creazione di oggetti di breve durata della classe Index.
mR_fr0g,

3
@ mR_fr0g non ti preoccupare, ho analizzato questo e creare tutti questi oggetti non è più lento del riutilizzare lo stesso oggetto per ogni iterazione. Il motivo è che tutti questi oggetti sono allocati solo nello spazio eden e mai la vita abbastanza a lungo per raggiungere l'heap. Quindi allocarli è veloce come ad esempio allocare variabili locali.
Akuhn,

1
@akuhn Aspetta. Lo spazio Eden non significa che non è necessario alcun GC. Al contrario, devi invocare il costruttore, scansionare prima con GC e finalizzare. Questo non solo carica la CPU ma invalida anche la cache in ogni momento. Niente di simile è necessario per le variabili locali. Perché dici che questo è "lo stesso"?
Val

7
Se sei preoccupato per l'esecuzione di oggetti di breve durata come questo, stai sbagliando. Le prestazioni dovrebbero essere l'ultima cosa di cui preoccuparsi ... leggibilità la prima. Questo è in realtà un costrutto abbastanza buono.
Bill K,

4
Stavo per dire che non capisco davvero la necessità di discutere quando l'istanza di Index può essere creata una volta e riutilizzata / aggiornata, solo per discutere l'argomento e rendere tutti felici. Tuttavia, nelle mie misurazioni, la versione che crea un nuovo Index () ogni volta ha eseguito più del doppio della mia macchina, quasi uguale a un iteratore nativo che esegue le stesse iterazioni.
Eric Woodruff,

47

La soluzione più semplice è semplicemente eseguire il proprio contatore in questo modo:

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

Il motivo è dovuto al fatto che non esiste alcuna garanzia concreta che gli articoli in una raccolta (su cui quella variante è forripetuta) abbiano persino un indice o addirittura un ordine definito (alcune raccolte possono cambiare l'ordine quando si aggiungono o rimuovono elementi).

Vedi ad esempio il seguente codice:

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

Quando lo esegui, puoi vedere qualcosa del tipo:

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

indicando che, giustamente, l'ordine non è considerato una caratteristica saliente di un insieme.

Ci sono altri modi per farlo senza un contatore manuale, ma è un bel po 'di lavoro a beneficio discutibile.


2
Questa sembra ancora essere la soluzione più chiara, anche con le interfacce List e Iterable.
Joshua Pinter,

27

L'uso di lambda e interfacce funzionali in Java 8 rende possibile la creazione di nuove astrazioni di loop. Posso passare in rassegna una raccolta con l'indice e le dimensioni della raccolta:

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

Quali uscite:

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

Che ho implementato come:

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

Le possibilità sono infinite. Ad esempio, creo un'astrazione che utilizza una funzione speciale solo per il primo elemento:

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

Che stampa correttamente un elenco separato da virgole:

one,two,three,four

Che ho implementato come:

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

Le biblioteche inizieranno a comparire per fare questo genere di cose, oppure puoi farlo tu stesso.


3
Non è una critica al post (è "il modo migliore per farlo" in questi giorni immagino), ma faccio fatica a vedere come questo è più facile / migliore di una semplice vecchia scuola per loop: for (int i = 0; i < list.size (); i ++) {} Anche la nonna potrebbe capirlo ed è probabilmente più facile da scrivere senza la sintassi e i caratteri non comuni che utilizza lambda. Non fraintendetemi, adoro usare lambda per certe cose (tipo di pattern di callback / gestore di eventi) ma non riesco proprio a capirne l'uso in casi come questo. Ottimo strumento, ma usandolo tutto il tempo , non posso proprio farlo.
Manius,

Suppongo, qualche evitamento di over / underflow dell'indice off-by-one contro l'elenco stesso. Ma c'è l'opzione pubblicata sopra: int i = 0; for (String s: stringArray) {doSomethingWith (s, i); i ++; }
Manius

21

Java 8 ha introdotto il metodo Iterable#forEach()/ Map#forEach(), che è più efficiente per molte Collection/ Mapimplementazioni rispetto al "classico" per ogni ciclo. Tuttavia, anche in questo caso non viene fornito un indice. Il trucco qui è usare AtomicIntegeral di fuori dell'espressione lambda. Nota: le variabili utilizzate nell'espressione lambda devono essere effettivamente finali, ecco perché non possiamo usare un normale int.

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

16

Temo che non sia possibile foreach. Ma posso suggerirti un semplice for-loop vecchio stile :

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

Si noti che, l' interfaccia Elenco consente di accedere a it.nextIndex().

(modificare)

Al tuo esempio modificato:

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

9

Uno dei cambiamenti che Sunsta prendendo in considerazione Java7è quello di fornire l'accesso all'interno dei Iteratorcircuiti foreach. la sintassi sarà simile a questa (se questa è accettata):

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

Questo è zucchero sintattico, ma a quanto pare sono state fatte molte richieste per questa funzione. Ma fino a quando non verrà approvato, dovrai contare tu stesso le iterazioni o utilizzare un ciclo for regolare con un Iterator.


7

Soluzione idiomatica:

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

questa è in realtà la soluzione che Google suggerisce nella discussione di Guava sul perché non hanno fornito un CountingIterator.


4

Sebbene ci siano così tanti altri modi per raggiungere lo stesso obiettivo, condividerò la mia strada per alcuni utenti insoddisfatti. Sto usando la funzione Java 8 IntStream.

1. Matrici

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. Elenco

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


1

C'è una "variante" alla risposta di pax ... ;-)

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

3
Curioso, c'è qualche vantaggio nell'usarlo rispetto i=0; i++;all'approccio apparentemente più chiaro ?
Joshua Pinter,

1
Immagino che se dovessi riutilizzare l'indice i dopo aver fatto qualcosa nel campo for.
gcedo,

1

Per situazioni in cui ho solo bisogno dell'indice di tanto in tanto, come in una clausola di cattura, a volte userò 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
Fai attenzione che questo richiede un'implementazione .equals () degli oggetti Arrays in grado di identificare un determinato oggetto in modo univoco. Questo non è il caso delle stringhe, se una determinata stringa si trova nell'array più volte, otterrai solo l'indice della prima occorrenza, anche se stavi già iterando su un elemento successivo con la stessa stringa.
Kosi2801

-1

La soluzione migliore e ottimizzata è fare quanto segue:

int i=0;

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

Dove Tipo può essere qualsiasi tipo di dati e tipi è la variabile a cui si sta facendo domanda per un ciclo.


Fornisci la tua risposta in un formato di codice riproducibile.
Nareman Darwish,

Questo è esattamente il modo in cui si desidera una soluzione alternativa. La domanda non riguarda i tipi ma le possibilità di accedere al contatore di loop in modo DIVERSO rispetto al conteggio manuale.
Kosi2801

-4

Sono un po 'sorpreso che nessuno abbia suggerito quanto segue (ammetto che è un approccio pigro ...); Se stringArray è un elenco di qualche tipo, è possibile utilizzare qualcosa come stringArray.indexOf (S) per restituire un valore per il conteggio corrente.

Nota: ciò presuppone che gli elementi dell'elenco siano univoci o che non importa se non sono univoci (perché in tal caso restituirà l'indice della prima copia trovata).

Ci sono situazioni in cui ciò sarebbe sufficiente ...


9
Con una performance che si avvicina a O (n²) per l'intero loop, è molto difficile immaginare anche alcune situazioni in cui ciò sarebbe superiore a qualsiasi altra cosa. Scusate.
Kosi2801,

-5

Ecco un esempio di come ho fatto questo. Questo ottiene l'indice in corrispondenza di ciascun ciclo. Spero che questo ti aiuti.

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
Siamo spiacenti, questo non risponde alla domanda. Non si tratta di accedere al contenuto, ma di un contatore di quanto spesso il ciclo è già stato ripetuto.
Kosi2801

La domanda era scoprire l'indice corrente nel ciclo in un ciclo per ogni stile.
rumman0786,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.