Existe uma maneira de acessar um contador de iteração no loop for-each do Java?

274

Existe uma maneira no loop for-each do Java

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

descobrir com que frequência o loop já foi processado?

Além de usar o antigo e bem conhecido for(int i=0; i < boundary; i++)loop, é a construção

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

a única maneira de ter esse contador disponível em um loop for-each?

Kosi2801
fonte
2
Outra pena é que você não pode usar a variável de loop fora do loop,Type var = null; for (var : set) dosomething; if (var != null) then ...
Val
@ Val, a menos que a referência seja efetiva final. Veja a minha resposta para saber como usar este recurso
rmuller

Respostas:

205

Não, mas você pode fornecer seu próprio balcão.

A razão para isso é que o loop for-each internamente não possui um contador; baseia-se na interface Iterable , ou seja, usa um Iteratorloop para percorrer a "coleção" - que pode não ser uma coleção e pode ser algo que não é baseado em índices (como uma lista vinculada).

Michael Borgwardt
fonte
5
Ruby tem uma construção para isso e Java deve obtê-lo também ...for(int idx=0, String s; s : stringArray; ++idx) doSomethingWith(s, idx);
Nicholas DiPiazza
9
A resposta deve começar com "Não, mas você pode fornecer seu próprio contador".
user1094206
1
FYI @NicholasDiPiazza existe um each_with_indexmétodo de loop Enumerableno Ruby. Veja apidock.com/ruby/Enumerable/each_with_index
saywhatnow
@saywhatnow sim é isso que eu quis dizer desculpe - não sei o que eu estava fumando quando eu fiz o comentário anterior
Nicholas DiPiazza
2
"A razão para isso é que o loop for-each internamente não possui um contador". Deve ser lido como "desenvolvedores Java não se importava de deixar programador especifique variável índice com valor inicial dentro forde loop", algo comofor (int i = 0; String s: stringArray; ++i)
izogfif
64

Existe outro caminho.

Dado que você escreve sua própria Indexclasse e um método estático que retorna Iterableinstâncias dessa classe, você pode

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

Onde a implementação de With.indexé algo 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
fonte
7
Boa ideia. Eu teria votado se não fosse a criação de objetos de curta duração da classe Index.
MR_fr0g
3
@ mR_fr0g não se preocupe, avaliei isso e criar todos esses objetos não é mais lento do que reutilizar o mesmo objeto para cada iteração. O motivo é que todos esses objetos são alocados apenas no espaço eden e nunca duram o suficiente para alcançar a pilha. Portanto, alocá-los é tão rápido quanto, por exemplo, alocar variáveis ​​locais.
akuhn
1
@akuhn Espere. O espaço Eden não significa que nenhum GC seja adquirido. Muito pelo contrário, você precisa chamar o construtor, digitalizar mais cedo com o GC e finalizar. Isso não apenas carrega a CPU, mas também invalida o cache o tempo todo. Nada disso é necessário para variáveis ​​locais. Por que você diz que isso é "o mesmo"?
Val
7
Se você está preocupado com o desempenho de objetos de vida curta como esse, está fazendo errado. O desempenho deve ser a ÚLTIMA coisa para se preocupar ... a legibilidade primeiro. Esta é realmente uma construção muito boa.
Bill K
4
Eu diria que realmente não entendo a necessidade de debater quando a instância do Index pode ser criada uma vez e reutilizada / atualizada, apenas para discutir o argumento e fazer todo mundo feliz. No entanto, nas minhas medidas, a versão que cria um novo Index () cada vez é executada duas vezes mais rápido na minha máquina, quase igual a um iterador nativo executando as mesmas iterações.
Eric Woodruff
47

A solução mais fácil é executar o seu próprio contador assim:

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

O motivo disso é que não há garantia real de que os itens de uma coleção (sobre os quais a variante foritera) tenham um índice ou mesmo uma ordem definida (algumas coleções podem alterar a ordem quando você adiciona ou remove elementos).

Veja, por exemplo, o seguinte 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");
  }
}

Quando você executa isso, pode 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, com razão, a ordem não é considerada uma característica marcante de um conjunto.

Existem outras maneiras de fazer isso sem um contador manual, mas é um bom trabalho para benefício duvidoso.

paxdiablo
fonte
2
Essa ainda parece ser a solução mais clara, mesmo com as interfaces List e Iterable.
Joshua Pinter
27

O uso de lambdas e interfaces funcionais no Java 8 possibilita a criação de novas abstrações de loop. Eu posso repetir uma coleção com o índice e o tamanho da coleção:

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

Quais saídas:

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

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

As possibilidades são infinitas. Por exemplo, eu crio uma abstração que usa uma função especial apenas para o primeiro elemento:

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

Que imprime uma lista separada por vírgula corretamente:

one,two,three,four

Que eu implementei 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);
      }
   }
}

As bibliotecas começarão a aparecer para fazer esse tipo de coisa, ou você pode criar suas próprias.

James Scriven
fonte
3
Não é uma crítica ao post (acho que é "a maneira legal de fazer isso" hoje em dia), mas luto para ver como isso é mais fácil / melhor do que uma simples velha escola para loop: for (int i = 0; i < list.size (); i ++) {} Até a vovó pode entender isso e provavelmente é mais fácil digitar sem a sintaxe e os caracteres incomuns que um lambda usa. Não me interpretem mal, eu amo usar lambdas para certas coisas (tipo de padrão de retorno de chamada / manipulador de eventos), mas simplesmente não consigo entender o uso em casos como este. Ótima ferramenta, mas usando o tempo todo , simplesmente não consigo.
Manius
Suponho que alguma evitação do índice off-by-one exceda / subflua contra a própria lista. Mas existe a opção postada acima: int i = 0; for (String s: stringArray) {doSomethingWith (s, i); i ++; }
Manius
21

O Java 8 introduziu o método Iterable#forEach()/ Map#forEach(), que é mais eficiente para muitas Collection/ Mapimplementações comparado ao "clássico" para cada loop. No entanto, também neste caso, um índice não é fornecido. O truque aqui é usar AtomicIntegerfora da expressão lambda. Nota: as variáveis ​​usadas na expressão lambda devem ser efetivamente finais, por isso não podemos usar um comum int.

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

Receio que isso não seja possível foreach. Mas posso sugerir-lhe um simples estilo antigo de loops :

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

Observe que, a interface da lista dá acesso a it.nextIndex().

(editar)

Para o seu exemplo alterado:

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

Uma das mudanças que Sunestá considerando Java7é fornecer acesso aos Iteratorloops foreach internos . a sintaxe será mais ou menos assim (se isso for aceito):

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

Isso é açúcar sintático, mas aparentemente muitos pedidos foram feitos para esse recurso. Mas até que seja aprovado, você precisará contar as iterações ou usar um loop for regular com um Iterator.

Yuval
fonte
7

Solução Idiomatic:

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 é realmente a solução que o Google sugere na discussão do Guava sobre por que eles não forneceram a CountingIterator.

Comunidade
fonte
4

Embora existam muitas outras maneiras mencionadas para alcançar o mesmo, compartilharei o meu caminho com alguns usuários insatisfeitos. Estou usando o recurso Java 8 IntStream.

1. Matrizes

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

Se você precisar de um contador em um loop for-each, precisará contar a si mesmo. Não existe um balcão embutido, até onde eu saiba.

EricSchaefer
fonte
(Eu corrigi esse bug na pergunta.) #
Tom Hawtin - tackline 25/01/09
1

Existe uma "variante" para pax 'responder ... ;-)

int i = -1;
for(String s : stringArray) {
    doSomethingWith(s, ++i);
}
Jörg
fonte
3
Curioso, existe algum benefício em usar isso sobre a i=0; i++;abordagem aparentemente mais clara ?
Joshua Pinter
1
Eu acho que se você precisar usar novamente o índice i após doSomething no escopo for.
Gcedo #
1

Para situações em que eu só preciso do índice ocasionalmente, como em uma cláusula catch, algumas vezes usarei o 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
fonte
2
Tenha cuidado para que isso precise de uma implementação .equals () dos objetos Arrays que possam identificar um determinado objeto exclusivamente. Esse não é o caso de Strings, se uma certa string estiver na matriz várias vezes, você obterá apenas o índice da primeira ocorrência, mesmo se você já estivesse iterando em um item posterior com a mesma string.
Kosi2801
-1

A solução melhor e otimizada é fazer o seguinte:

int i=0;

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

Onde Type pode ser qualquer tipo e tipo de dados é a variável na qual você está aplicando para um loop.

Abhijeet Chopra
fonte
Forneça sua resposta em um formato de código reproduzível.
Nareman Darwish
É exatamente dessa maneira que uma solução alternativa é desejada. A questão não é sobre tipos, mas sobre possibilidades de acessar o contador de loop de uma maneira DIFERENTE do que contar manualmente.
Kosi2801 16/02
-4

Estou um pouco surpreso que ninguém tenha sugerido o seguinte (admito que seja uma abordagem preguiçosa ...); Se stringArray for uma lista de algum tipo, você poderá usar algo como stringArray.indexOf (S) para retornar um valor para a contagem atual.

Nota: isso pressupõe que os elementos da Lista são únicos ou que não importa se não são únicos (porque nesse caso, ele retornará o índice da primeira cópia encontrada).

Existem situações em que isso seria suficiente ...

Rik
fonte
9
Com um desempenho próximo de O (n²) para todo o loop, é muito difícil imaginar até mesmo algumas situações em que isso seria superior a qualquer outra coisa. Desculpe.
precisa saber é o seguinte
-5

Aqui está um exemplo de como eu fiz isso. Isso obtém o índice no para cada loop. Espero que isto ajude.

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
fonte
1
Desculpe, isso não está respondendo à pergunta. Não se trata de acessar o conteúdo, mas de um contador com que frequência o loop já foi iterado.
precisa saber é o seguinte
A questão era descobrir o índice atual no loop em um loop para cada estilo.
Rumman0786 17/07/19