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?


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


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

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.


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.


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

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

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.


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.


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


1

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

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

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

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.


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


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


    }
}

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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.