¿Hay alguna manera de acceder a un contador de iteraciones en el ciclo for-each de Java?

274

¿Hay alguna manera en el ciclo for-each de Java?

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

para saber con qué frecuencia el ciclo ya ha sido procesado?

Además de usar el viejo y conocido for(int i=0; i < boundary; i++)bucle, es la construcción

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

¿Cuál es la única manera de tener dicho contador disponible en un ciclo para cada uno?

Kosi2801
fuente
2
Otra lástima es que no puedes usar la variable de bucle fuera del bucle,Type var = null; for (var : set) dosomething; if (var != null) then ...
Val
@Val a menos que la referencia sea efectiva final. Vea mi respuesta sobre cómo usar esta función
rmuller

Respuestas:

205

No, pero puedes proporcionar tu propio contador.

La razón de esto es que el ciclo for-each internamente no tiene un contador; se basa en la interfaz Iterable , es decir, utiliza un Iteratorbucle a través de la "colección", que puede no ser una colección en absoluto, y de hecho puede ser algo que no se basa en ningún índice (como una lista vinculada).

Michael Borgwardt
fuente
55
Ruby tiene una construcción para esto y Java también debería for(int idx=0, String s; s : stringArray; ++idx) doSomethingWith(s, idx);
tenerla
9
La respuesta debería comenzar con "No, pero puede proporcionar su propio contador".
user1094206
1
Para su información @NicholasDiPiazza hay un each_with_indexmétodo de bucle Enumerableen Ruby. Ver apidock.com/ruby/Enumerable/each_with_index
saywhatnow
@saywhatnow, sí, eso es lo que quise decir, lo siento, no estoy seguro de lo que estaba fumando cuando hice el comentario anterior
Nicholas DiPiazza, el
2
"La razón de esto es que el ciclo for-each internamente no tiene un contador". Debe leerse como "a los desarrolladores de Java no les importaba dejar que el programador especificara la variable de índice con el valor inicial dentro del forciclo", algo así comofor (int i = 0; String s: stringArray; ++i)
izogfif
64

Hay otra manera

Dado que usted escribe su propia Indexclase y un método estático que devuelve una Iterablesobre instancias de esta clase, puede

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

Donde la implementación de With.indexes algo así como

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
fuente
77
Buena idea. Hubiera votado si no hubiera sido por la creación de objetos de corta duración de la clase Index.
mR_fr0g
3
@ mR_fr0g no se preocupe, comparé esto y crear todos estos objetos no es más lento que reutilizar el mismo objeto para cada iteración. La razón es que todos estos objetos se asignan solo en el espacio edén y nunca duran lo suficiente como para alcanzar el montón. Asignarlos es tan rápido como, por ejemplo, asignar variables locales.
akuhn
1
@akuhn Espera. El espacio Eden no significa que no se requiera GC. Al contrario, debe invocar al constructor, escanear antes con GC y finalizar. Esto no solo carga la CPU sino que también invalida el caché todo el tiempo. Nada de esto es necesario para las variables locales. ¿Por qué dices que esto es "lo mismo"?
Val
77
Si le preocupa el rendimiento de objetos de corta duración como este, lo está haciendo mal. El rendimiento debería ser la ÚLTIMA cosa de la que preocuparse ... la legibilidad es lo primero. Esto es realmente una muy buena construcción.
Bill K
44
Iba a decir que realmente no entiendo la necesidad de debatir cuándo la instancia de Index puede crearse una vez y reutilizarse / actualizarse, solo para discutir el argumento y hacer felices a todos. Sin embargo, en mis mediciones, la versión que crea un nuevo Index () cada vez funciona más del doble de rápido en mi máquina, casi igual a un iterador nativo que ejecuta las mismas iteraciones.
Eric Woodruff
47

La solución más fácil es simplemente ejecutar su propio contador así:

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

La razón de esto es porque no hay garantía real de que los elementos de una colección (que esa variante de foritera) incluso tengan un índice, o incluso tengan un orden definido (algunas colecciones pueden cambiar el orden cuando agrega o elimina elementos).

Vea, por ejemplo, el siguiente código:

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

Cuando ejecutas eso, puedes ver algo como:

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 que, con razón, el orden no se considera una característica destacada de un conjunto.

Hay otras formas de hacerlo sin un contador manual, pero es un buen trabajo para dudoso beneficio.

paxdiablo
fuente
2
Esta parece ser la solución más clara, incluso con las interfaces List e Iterable.
Joshua Pinter
27

El uso de lambdas e interfaces funcionales en Java 8 hace posible la creación de nuevas abstracciones de bucle. Puedo recorrer una colección con el índice y el tamaño de la colección:

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

Qué salidas:

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

Lo que implementé como:

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

Las posibilidades son infinitas. Por ejemplo, creo una abstracción que usa una función especial solo para el primer elemento:

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

Que imprime una lista separada por comas correctamente:

one,two,three,four

Lo que implementé como:

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

Las bibliotecas comenzarán a aparecer para hacer este tipo de cosas, o puede rodar las suyas.

James Scriven
fuente
3
No es una crítica de la publicación (supongo que es "la forma genial de hacerlo" en estos días), pero me cuesta ver cómo esto es más fácil / mejor que una simple vieja escuela para bucle: for (int i = 0; i < list.size (); i ++) {} Incluso la abuela podría entender esto y probablemente sea más fácil escribir sin la sintaxis y los caracteres poco comunes que usa una lambda. No me malinterpreten, me encanta usar lambdas para ciertas cosas (tipo de patrón de devolución de llamada / controlador de eventos) pero simplemente no puedo entender el uso en casos como este. Gran herramienta, pero usándola todo el tiempo , simplemente no puedo hacerlo.
Manius
Supongo que se evita el desbordamiento / desbordamiento del índice fuera de uno contra la lista misma. Pero existe la opción publicada anteriormente: int i = 0; for (String s: stringArray) {doSomethingWith (s, i); i ++; }
Manius
21

Java 8 introdujo el método Iterable#forEach()/ Map#forEach(), que es más eficiente para muchas implementaciones Collection/ en Mapcomparación con el ciclo "clásico" para cada bucle. Sin embargo, también en este caso no se proporciona un índice. El truco aquí es usar AtomicIntegerfuera de la expresión lambda. Nota: las variables utilizadas dentro de la expresión lambda deben ser efectivamente finales, es por eso que no podemos usar un ordinario int.

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

Me temo que esto no es posible con foreach. Pero puedo sugerirle unos simples bucles for de estilo antiguo :

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

Tenga en cuenta que la interfaz de Lista le da acceso a it.nextIndex().

(editar)

A su ejemplo cambiado:

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

Uno de los cambios que se Sunestá considerando Java7es proporcionar acceso al interior Iteratoren bucles foreach. la sintaxis será algo así (si se acepta):

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

Este es el azúcar sintáctico, pero aparentemente se hicieron muchas solicitudes para esta función. Pero hasta que se apruebe, tendrá que contar las iteraciones usted mismo o usar un ciclo regular para con un Iterator.

Yuval
fuente
7

Solución idiomática:

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

esta es en realidad la solución que Google sugiere en la discusión de Guava sobre por qué no proporcionaron una CountingIterator.

Comunidad
fuente
4

Aunque hay muchas otras formas mencionadas para lograr lo mismo, compartiré mi camino con algunos usuarios insatisfechos. Estoy usando la característica Java 8 IntStream.

1. Matrices

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
fuente
2

Si necesita un contador en un ciclo para cada uno, debe contar usted mismo. No hay un mostrador incorporado hasta donde yo sé.

EricSchaefer
fuente
(He arreglado ese error en la pregunta.)
Tom Hawtin - línea de entrada el
1

Hay una "variante" para la respuesta de pax ... ;-)

int i = -1;
for(String s : stringArray) {
    doSomethingWith(s, ++i);
}
Jörg
fuente
3
Curioso, ¿hay algún beneficio de usar esto sobre el i=0; i++;enfoque aparentemente más claro ?
Joshua Pinter
1
Supongo que si necesita volver a usar el índice i después de hacer algo en el alcance for.
gcedo
1

Para situaciones en las que solo necesito el índice ocasionalmente, como en una cláusula catch, a veces usaré 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
fuente
2
Tenga cuidado de que esto necesite una implementación .equals () de los objetos Arrays que pueden identificar un determinado objeto de manera única. Este no es el caso para las cadenas, si una determinada cadena está en la matriz varias veces, solo obtendrá el índice de la primera aparición, incluso si ya estaba iterando en un elemento posterior con la misma cadena.
Kosi2801
-1

La solución mejor y optimizada es hacer lo siguiente:

int i=0;

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

Donde Tipo puede ser cualquier tipo de datos y tipos es la variable en la que está solicitando un bucle.

Abhijeet Chopra
fuente
Proporcione su respuesta en un formato de código reproducible.
Nareman Darwish
Esta es exactamente la forma en que se busca una solución alternativa. La pregunta no es sobre los tipos, sino sobre las posibilidades de acceder al contador de bucle de una manera DIFERENTE que contar manualmente.
Kosi2801
-4

Estoy un poco sorprendido de que nadie sugirió lo siguiente (admito que es un enfoque perezoso ...); Si stringArray es una lista de algún tipo, podría usar algo como stringArray.indexOf (S) para devolver un valor para el recuento actual.

Nota: esto supone que los elementos de la Lista son únicos, o que no importa si no son únicos (porque en ese caso devolverá el índice de la primera copia encontrada).

Hay situaciones en las que eso sería suficiente ...

Rik
fuente
9
Con un rendimiento cercano a O (n²) para todo el ciclo, es muy difícil imaginar incluso algunas situaciones en las que esto sería superior a cualquier otra cosa. Lo siento.
Kosi2801
-5

Aquí hay un ejemplo de cómo hice esto. Esto obtiene el índice en cada ciclo. Espero que esto ayude.

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
fuente
1
Lo siento, esto no responde la pregunta. No se trata de acceder al contenido, sino de un contador de la frecuencia con la que el ciclo ya se ha iterado.
Kosi2801
La pregunta era encontrar el índice actual en el ciclo en un ciclo de estilo para cada uno.
rumman0786