Sendo um pouco novo para a linguagem Java, estou tentando me familiarizar com todas as maneiras (ou pelo menos as não-patológicas) em que se pode percorrer uma lista (ou talvez outras coleções) e as vantagens ou desvantagens de cada uma.
Dado um List<E> list
objeto, conheço as seguintes maneiras de percorrer todos os elementos:
Básico para loop (é claro, também existem equivalentes while
/ do while
loops)
// Not recommended (see below)!
for (int i = 0; i < list.size(); i++) {
E element = list.get(i);
// 1 - can call methods of element
// 2 - can use 'i' to make index-based calls to methods of list
// ...
}
Nota: Como o @amarseillan apontou, este formulário é uma má escolha para iterar sobre List
s, porque a implementação real do get
método pode não ser tão eficiente quanto ao usar um Iterator
. Por exemplo, as LinkedList
implementações devem atravessar todos os elementos anteriores a i para obter o i-ésimo elemento.
No exemplo acima, não há como a List
implementação "salvar seu lugar" para tornar as iterações futuras mais eficientes. Para um ArrayList
, isso realmente não importa, porque a complexidade / custo de get
é tempo constante (O (1)), enquanto para a LinkedList
é proporcional ao tamanho da lista (O (n)).
Para obter mais informações sobre a complexidade computacional das Collections
implementações internas, consulte esta pergunta .
Aprimorado para loop (explicado detalhadamente nesta pergunta )
for (E element : list) {
// 1 - can call methods of element
// ...
}
Iterador
for (Iterator<E> iter = list.iterator(); iter.hasNext(); ) {
E element = iter.next();
// 1 - can call methods of element
// 2 - can use iter.remove() to remove the current element from the list
// ...
}
ListIterator
for (ListIterator<E> iter = list.listIterator(); iter.hasNext(); ) {
E element = iter.next();
// 1 - can call methods of element
// 2 - can use iter.remove() to remove the current element from the list
// 3 - can use iter.add(...) to insert a new element into the list
// between element and iter->next()
// 4 - can use iter.set(...) to replace the current element
// ...
}
Java funcional
list.stream().map(e -> e + 1); // Can apply a transformation function for e
Iterable.forEach , Stream.forEach , ...
(Um método de mapa da API Stream do Java 8 (consulte a resposta de @i_am_zero).)
No Java 8, as classes de coleção que implementam Iterable
(por exemplo, todos os List
s) agora têm um forEach
método, que pode ser usado em vez da instrução de loop for demonstrada acima. (Aqui está outra pergunta que fornece uma boa comparação.)
Arrays.asList(1,2,3,4).forEach(System.out::println);
// 1 - can call methods of an element
// 2 - would need reference to containing object to remove an item
// (TODO: someone please confirm / deny this)
// 3 - functionally separates iteration from the action
// being performed with each item.
Arrays.asList(1,2,3,4).stream().forEach(System.out::println);
// Same capabilities as above plus potentially greater
// utilization of parallelism
// (caution: consequently, order of execution is not guaranteed,
// see [Stream.forEachOrdered][stream-foreach-ordered] for more
// information about this).
Que outras maneiras existem, se houver?
(BTW, meu interesse não resulta de um desejo de otimizar o desempenho ; eu só quero saber quais formulários estão disponíveis para mim como desenvolvedor.)
List
interface é específica?Respostas:
As três formas de loop são quase idênticas. O
for
loop aprimorado :é, de acordo com a Java Language Specification , idêntico ao uso explícito de um iterador com um
for
loop tradicional . No terceiro caso, você só pode modificar o conteúdo da lista removendo o elemento atual e, somente se o fizer através doremove
método do iterador. Com a iteração baseada em índice, você pode modificar a lista de qualquer maneira. No entanto, adicionar ou remover elementos anteriores ao índice atual corre o risco de o loop ignorar elementos ou processar o mesmo elemento várias vezes; você precisa ajustar o índice do loop corretamente quando fizer essas alterações.Em todos os casos,
element
é uma referência ao elemento da lista real. Nenhum dos métodos de iteração faz uma cópia de qualquer coisa na lista. As alterações no estado interno deelement
sempre serão vistas no estado interno do elemento correspondente na lista.Essencialmente, existem apenas duas maneiras de iterar em uma lista: usando um índice ou um iterador. O loop for aprimorado é apenas um atalho sintático introduzido no Java 5 para evitar o tédio de definir explicitamente um iterador. Para ambos os estilos, você pode criar variações essencialmente triviais usando
for
,while
oudo while
blocos, mas todas se resumem à mesma coisa (ou melhor, duas coisas).EDIT: Como o @ iX3 aponta em um comentário, você pode usar a
ListIterator
para definir o elemento atual de uma lista enquanto estiver iterando. Você precisaria usar emList#listIterator()
vez deList#iterator()
inicializar a variável de loop (que, obviamente, teria que ser declarada um emListIterator
vez de umIterator
).fonte
e = iterator.next()
,e = somethingElse
apenas altere a que objetoe
se refere, em vez de alterar o armazenamento real a partir do qualiterator.next()
o valor foi recuperado.e
não altera o que está na lista; você tem que ligarlist.set(index, thing)
. Você pode alterar o conteúdo dee
(por exemplo,e.setSomething(newValue)
), mas para alterar qual elemento é armazenado na lista enquanto você o itera, você precisa se ater a uma iteração baseada em índice.e
eu teria que chamar um dose
métodos porque a atribuição apenas altera o ponteiro (perdoe meu C).Integer
eString
não seria possível alterar o conteúdo usandofor-each
ouIterator
métodos - teria que manipular o próprio objeto da lista para substituir os elementos. Isso está certo?Exemplo de cada tipo listado na pergunta:
ListIterationExample.java
fonte
O loop básico não é recomendado, pois você não conhece a implementação da lista.
Se fosse um LinkedList, cada chamada para
estaria iterando sobre a lista, resultando em complexidade de tempo N ^ 2.
fonte
Uma iteração no estilo JDK8:
fonte
No Java 8 , temos várias maneiras de iterar sobre as classes de coleção.
Usando o Iterable forEach
As coleções que implementam
Iterable
(por exemplo, todas as listas) agora têmforEach
método. Podemos usar a referência de método introduzida no Java 8.Usando Streams forEach e forEachOrdered
Também podemos iterar sobre uma lista usando o Stream como:
Deveríamos preferir o
forEachOrdered
contrário,forEach
porque o comportamento deforEach
é explicitamente não determinístico, onde, como eleforEachOrdered
executa uma ação para cada elemento desse fluxo, na ordem de encontro do fluxo, se o fluxo tiver uma ordem de encontro definida. Portanto, o forEach não garante que o pedido seja mantido.A vantagem dos fluxos é que também podemos usar fluxos paralelos sempre que apropriado. Se o objetivo é apenas imprimir os itens, independentemente da ordem, podemos usar o fluxo paralelo como:
fonte
Não sei o que você considera patológico, mas deixe-me fornecer algumas alternativas que você nunca viu antes:
Ou sua versão recursiva:
Além disso, uma versão recursiva do clássico
for(int i=0...
:Eu os mencionei porque você é "um pouco novo em Java" e isso pode ser interessante.
fonte
while(!copyList.isEmpty()){ E e = copyList.remove(0); ... }
. Isso é mais eficaz que a primeira versão;).Você pode usar o forEach a partir do Java 8:
fonte
Para uma pesquisa reversa, você deve usar o seguinte:
Se você deseja conhecer uma posição, use iterator.previousIndex (). Também ajuda a escrever um loop interno que compara duas posições na lista (os iteradores não são iguais).
fonte
Certo, muitas alternativas estão listadas. O mais fácil e limpo seria usar apenas a
for
declaração aprimorada , conforme abaixo. OExpression
é de algum tipo que é iterável.Por exemplo, para iterar por meio de List <String> ids, podemos simplesmente fazer isso,
fonte
Em
java 8
você pode usar oList.forEach()
método comlambda expression
para iterar sobre uma lista.fonte
eugene82
resposta ei_am_zero
da resposta ?Você sempre pode alternar o primeiro e o terceiro exemplos com um loop while e um pouco mais de código. Isso oferece a vantagem de poder usar o tempo de ação:
Obviamente, esse tipo de coisa pode causar uma NullPointerException se o list.size () retornar 0, pois sempre é executado pelo menos uma vez. Isso pode ser corrigido testando se o elemento é nulo antes de usar seus atributos / métodos. Ainda assim, é muito mais simples e fácil usar o loop for
fonte