Czy istnieje sposób na uzyskanie dostępu do licznika iteracji w pętli Java dla każdej z nich?

274

Czy istnieje sposób dla każdej pętli Java

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

aby dowiedzieć się, jak często pętla była już przetwarzana?

Oprócz użycia starej i dobrze znanej for(int i=0; i < boundary; i++)pętli, jest to konstrukcja

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

jedyny sposób, aby taki licznik był dostępny w pętli dla każdego?

Kosi2801
źródło
2
Inna szkoda, że ​​nie można używać zmiennej pętli poza pętlą,Type var = null; for (var : set) dosomething; if (var != null) then ...
Val
@ Val, chyba że odniesienie jest skuteczne, ostateczne. Zobacz moją odpowiedź na temat korzystania z tej funkcji
rmuller

Odpowiedzi:

205

Nie, ale możesz podać własny licznik.

Powodem tego jest to, że wewnętrznie pętla dla każdego nie ma licznika; opiera się na interfejsie Iterable , tzn. wykorzystuje Iteratorpętlę do przechodzenia przez „kolekcję” - która może wcale nie być kolekcją, i może w rzeczywistości być czymś zupełnie nie opartym na indeksach (takich jak lista połączona).

Michael Borgwardt
źródło
5
Ruby ma do tego konstrukcję, a Java też powinna ją zdobyć ...for(int idx=0, String s; s : stringArray; ++idx) doSomethingWith(s, idx);
Nicholas DiPiazza
9
Odpowiedź powinna zaczynać się od „Nie, ale możesz podać własny licznik”.
user1094206,
1
FYI @NicholasDiPiazza w Ruby istnieje each_with_indexmetoda pętli Enumerable. Zobacz apidock.com/ruby/Enumerable/each_with_index
saywhatnow
@aywhatnow tak właśnie to miałem na myśli przepraszam - nie jestem pewien, co paliłem, kiedy zrobiłem poprzedni komentarz
Nicholas DiPiazza
2
„Powodem tego jest to, że wewnętrzna pętla dla każdego nie ma licznika”. Należy czytać jako „programistów Java nie obchodzi pozwolić programista określić indeks zmiennej z wartością początkową wewnątrz forpętli”, coś takiegofor (int i = 0; String s: stringArray; ++i)
izogfif
64

Jest inny sposób.

Biorąc pod uwagę, że piszesz własną Indexklasę i metodę statyczną, która zwraca Iterableponad instancje tej klasy, możesz

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

Gdzie wdrożenie With.indexjest podobne

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
źródło
7
Zgrabny pomysł. Byłbym pozytywnie oceniany, gdyby nie stworzenie krótkotrwałego obiektu klasy Index.
mR_fr0g
3
@ mR_fr0g nie martw się, dokonałem testu porównawczego i utworzenie wszystkich tych obiektów nie jest wolniejsze niż ponowne użycie tego samego obiektu dla każdej iteracji. Powodem jest to, że wszystkie te obiekty są przydzielane tylko w przestrzeni eden i nigdy nie są wystarczająco długie, aby dosięgnąć stosu. Przydzielanie ich jest więc tak szybkie, jak np. Przydzielanie zmiennych lokalnych.
akuhn
1
@akuhn Czekaj. Miejsce w Eden nie oznacza, że ​​nie jest wymagana żadna GC. Wręcz przeciwnie, musisz wywołać konstruktora, wcześniej skanować za pomocą GC i sfinalizować. To nie tylko ładuje procesor, ale także cały czas unieważnia pamięć podręczną. Nic takiego nie jest konieczne w przypadku zmiennych lokalnych. Dlaczego mówisz, że to „to samo”?
Val
7
Jeśli martwisz się wydajnością takich krótko żyjących obiektów, robisz to źle. Wydajność powinna być OSTATNĄ rzeczą, o którą należy się martwić ... czytelnością jako pierwszą. To właściwie całkiem niezła konstrukcja.
Bill K
4
Chciałem powiedzieć, że tak naprawdę nie rozumiem potrzeby debaty, kiedy instancja indeksu może zostać utworzona raz i ponownie użyta / zaktualizowana, tylko po to, aby podważyć argument i uszczęśliwić wszystkich. Jednak w moich pomiarach wersja, która tworzy nowy indeks (), za każdym razem działała ponad dwukrotnie szybciej na moim komputerze, mniej więcej tak samo, jak natywny iterator z tymi samymi iteracjami.
Eric Woodruff,
47

Najłatwiejszym rozwiązaniem jest uruchomienie własnego licznika w ten sposób:

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

Powodem tego jest fakt, że nie ma rzeczywistej gwarancji, że przedmioty w kolekcji (nad którymi ten wariant się foriteruje) mają nawet indeks lub nawet określoną kolejność (niektóre kolekcje mogą zmieniać kolejność podczas dodawania lub usuwania elementów).

Zobacz na przykład następujący kod:

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

Po uruchomieniu możesz zobaczyć coś takiego:

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

wskazując, że słusznie, porządek nie jest uważany za istotną cechę zestawu.

Istnieją inne sposoby, aby to zrobić bez ręcznego licznika, ale jest to sporo pracy dla wątpliwej korzyści.

paxdiablo
źródło
2
To nadal wydaje się być najczystszym rozwiązaniem, nawet w przypadku interfejsów List i Iterable.
Joshua Pinter,
27

Korzystanie z lambd i interfejsów funkcjonalnych w Javie 8 umożliwia tworzenie nowych abstrakcji pętli. Mogę zapętlać kolekcję z indeksem i rozmiarem kolekcji:

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

Które wyjścia:

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

Które wdrożyłem jako:

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

Możliwości są nieskończone. Na przykład tworzę abstrakcję, która używa specjalnej funkcji tylko dla pierwszego elementu:

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

Która drukuje poprawnie listę rozdzielaną przecinkami:

one,two,three,four

Które wdrożyłem jako:

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

Biblioteki zaczną pojawiać się, aby robić takie rzeczy, lub możesz tworzyć własne.

James Scriven
źródło
3
To nie jest krytyka postu (to chyba „fajny sposób na zrobienie tego”), ale staram się zobaczyć, jak to jest łatwiejsze / lepsze niż prosta stara szkoła dla pętli: for (int i = 0; i < list.size (); i ++) {} Nawet babcia może to zrozumieć i prawdopodobnie łatwiej jest pisać bez składni i nietypowych znaków, których używa lambda. Nie zrozum mnie źle, uwielbiam używać lambdas do pewnych rzeczy (typ wzorca wywołania zwrotnego / modułu obsługi zdarzeń), ale po prostu nie rozumiem zastosowania w takich przypadkach. Świetne narzędzie, ale używam go cały czas , po prostu nie mogę.
Manius
Podejrzewam, że pewne uniknięcie nadmiernego / niedopełnienia indeksu off-by-one względem samej listy. Ale jest opcja zamieszczona powyżej: int i = 0; for (String s: stringArray) {doSomethingWith (s, i); i ++; }
Manius
21

Java 8 wprowadziła metodę Iterable#forEach()/ Map#forEach(), która jest wydajniejsza dla wielu implementacji Collection/ w Mapporównaniu z „klasyczną” dla każdej pętli. Jednak również w tym przypadku nie podano indeksu. Sztuką jest użycie AtomicIntegerpoza wyrażeniem lambda. Uwaga: zmienne użyte w wyrażeniu lambda muszą być faktycznie ostateczne, dlatego nie możemy użyć zwykłego int.

final AtomicInteger indexHolder = new AtomicInteger();
map.forEach((k, v) -> {
    final int index = indexHolder.getAndIncrement();
    // use the index
});
rmuller
źródło
16

Obawiam się, że nie jest to możliwe foreach. Ale mogę zasugerować ci proste pętle w starym stylu :

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

Zauważ, że interfejs listy daje dostęp do it.nextIndex().

(edytować)

Do zmienionego przykładu:

    for(ListIterator<String> it =l.listIterator(); it.hasNext() ;)
    {
        int i = it.nextIndex();
        doSomethingWith(it.next(), i);
    }
Bruno Conde
źródło
9

Jedną z Sunrozważanych zmian jest Java7zapewnienie dostępu do wewnętrznej Iteratorpętli foreach. składnia będzie mniej więcej taka (jeśli zostanie to zaakceptowane):

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

Jest to cukier składniowy, ale najwyraźniej zgłoszono wiele żądań dotyczących tej funkcji. Ale dopóki nie zostanie zatwierdzony, będziesz musiał sam policzyć iteracje lub użyć zwykłej pętli for z Iterator.

Yuval
źródło
7

Rozwiązanie idiomatyczne:

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

jest to w rzeczywistości rozwiązanie, które Google sugeruje w dyskusji na Guava, dlaczego nie dostarczył CountingIterator.

Społeczność
źródło
4

Chociaż istnieje tak wiele innych sposobów osiągnięcia tego samego, podzielę się swoją drogą dla niektórych niezadowolonych użytkowników. Korzystam z funkcji IntStream Java 8.

1. Tablice

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

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
źródło
2

Jeśli potrzebujesz licznika w pętli dla każdego, musisz liczyć sam. O ile mi wiadomo, nie ma wbudowanego licznika.

EricSchaefer
źródło
(Naprawiłem ten błąd w pytaniu.)
Tom Hawtin - zgłoś
1

Istnieje „wariant” odpowiedzi na pax… ;-)

int i = -1;
for(String s : stringArray) {
    doSomethingWith(s, ++i);
}
Jörg
źródło
3
Ciekawe, czy jest jakaś korzyść z zastosowania tego w porównaniu z pozornie jaśniejszym i=0; i++;podejściem?
Joshua Pinter,
1
Myślę, że jeśli musisz ponownie użyć indeksu i po doSomething w zakresie for.
gcedo
1

W sytuacjach, gdy potrzebuję indeksu tylko od czasu do czasu, np. W klauzuli catch, czasami używam 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
źródło
2
Uważaj, że wymaga to implementacji .equals () - obiektów Arrays, które mogą jednoznacznie zidentyfikować określony obiekt. Nie dotyczy to ciągów, jeśli określony ciąg znaków jest wielokrotnie w tablicy, indeks pierwszego wystąpienia pojawi się tylko wtedy, gdy już iterowałeś na późniejszym elemencie z tym samym ciągiem.
Kosi2801
-1

Najlepszym i zoptymalizowanym rozwiązaniem jest wykonanie następujących czynności:

int i=0;

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

Gdzie Typ może być dowolnym typem danych, a typami jest zmienna, o którą ubiegasz się o pętlę.

Abhijeet Chopra
źródło
Podaj swoją odpowiedź w formacie odtwarzalnego kodu.
Nareman Darwish
Właśnie w ten sposób potrzebne jest alternatywne rozwiązanie. Pytanie nie dotyczy typów, ale możliwości dostępu do licznika pętli w inny sposób niż liczenie ręczne.
Kosi2801
-4

Jestem trochę zaskoczony, nikt nie sugerował, co następuje (przyznaję, że jest to leniwe podejście ...); Jeśli stringArray jest pewnego rodzaju Listą, możesz użyć czegoś takiego jak stringArray.indexOf (S), aby zwrócić wartość dla bieżącej liczby.

Uwaga: zakłada to, że elementy Listy są unikalne lub że nie ma znaczenia, czy nie są one unikalne (ponieważ w takim przypadku zwróci indeks pierwszej znalezionej kopii).

Są sytuacje, w których byłoby to wystarczające ...

Rik
źródło
9
Przy wydajności zbliżającej się do O (n²) dla całej pętli bardzo trudno jest wyobrazić sobie nawet kilka sytuacji, w których byłoby to lepsze niż cokolwiek innego. Przepraszam.
Kosi2801
-5

Oto przykład tego, jak to zrobiłem. Otrzymuje indeks dla każdej pętli. Mam nadzieję że to pomoże.

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
źródło
1
Przepraszamy, to nie odpowiada na pytanie. Nie chodzi o dostęp do treści, ale o licznik, jak często pętla jest już iterowana.
Kosi2801
Pytanie polegało na znalezieniu bieżącego indeksu w pętli w pętli dla każdego stylu.
rumman0786,