Będąc nieco nowym językiem Java, próbuję zapoznać się ze wszystkimi sposobami (a przynajmniej niepatologicznymi), które można iterować po liście (lub innych kolekcjach) oraz zaletami i wadami każdego z nich.
Biorąc pod uwagę List<E> list
obiekt, znam następujące sposoby przechodzenia przez wszystkie elementy:
Podstawowy dla pętli (oczywiście istnieją również odpowiedniki while
/ do while
pętle)
// 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
// ...
}
Uwaga: Jak zauważył @amarseillan, ta forma jest kiepskim wyborem dla iteracji w porównaniu z List
s, ponieważ faktyczna implementacja get
metody może nie być tak wydajna jak w przypadku korzystania z Iterator
. Na przykład LinkedList
implementacje muszą przechodzić przez wszystkie elementy poprzedzające i, aby uzyskać i-ty element.
W powyższym przykładzie List
implementacja nie może „zapisać swojego miejsca”, aby zwiększyć wydajność przyszłych iteracji. Dla a ArrayList
tak naprawdę nie ma to znaczenia, ponieważ złożoność / koszt get
to stały czas (O (1)), podczas gdy dla a LinkedList
jest proporcjonalny do wielkości listy (O (n)).
Aby uzyskać więcej informacji na temat złożoności obliczeniowej wbudowanych Collections
implementacji, sprawdź to pytanie .
Ulepszone dla pętli (ładnie wyjaśnione w tym pytaniu )
for (E element : list) {
// 1 - can call methods of element
// ...
}
Iterator
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
// ...
}
Funkcjonalna Java
list.stream().map(e -> e + 1); // Can apply a transformation function for e
Iterable.forEach , Stream.forEach ...
(Metoda mapy z Stream API Java 8 (patrz odpowiedź @ i_am_zero).)
W klasach Java 8, które implementują Iterable
(na przykład wszystkie List
s), istnieje teraz forEach
metoda, której można użyć zamiast instrukcji pętli for pokazanej powyżej. (Oto kolejne pytanie, które zapewnia dobre porównanie).
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).
Jakie są inne sposoby, jeśli istnieją?
(BTW, moje zainteresowanie wcale nie wynika z chęci optymalizacji wydajności ; chcę tylko wiedzieć, jakie formy są dostępne jako programista).
List
interfejsu jest specyficzne?Odpowiedzi:
Trzy formy zapętlenia są prawie identyczne. Ulepszona
for
pętla:jest, zgodnie ze specyfikacją języka Java , identyczny z wyraźnym zastosowaniem iteratora z tradycyjną
for
pętlą. W trzecim przypadku możesz zmodyfikować zawartość listy tylko poprzez usunięcie bieżącego elementu, a następnie tylko wtedy, gdy zrobisz to za pomocą samejremove
metody iteratora. Dzięki iteracji opartej na indeksie możesz dowolnie modyfikować listę. Jednak dodawanie lub usuwanie elementów poprzedzających bieżący indeks grozi pomijaniem elementów w pętli lub przetwarzaniem tego samego elementu wiele razy; podczas dokonywania takich zmian należy odpowiednio dostosować indeks pętli.We wszystkich przypadkach
element
jest odwołaniem do rzeczywistego elementu listy. Żadna z metod iteracji nie tworzy kopii niczego na liście. Zmiany stanu wewnętrznegoelement
zawsze będą widoczne w stanie wewnętrznym odpowiedniego elementu na liście.Zasadniczo istnieją tylko dwa sposoby iteracji po liście: za pomocą indeksu lub iteratora. Ulepszona pętla for to po prostu skrót składniowy wprowadzony w Javie 5, aby uniknąć nudy jawnego definiowania iteratora. Dla obu stylów, można wymyślić zasadniczo błahe wariacji użyciem
for
,while
czydo while
bloki, ale wszystkie one sprowadzają się do tego samego (albo raczej dwie rzeczy).EDYCJA: Jak wskazuje @ iX3 w komentarzu, możesz użyć a,
ListIterator
aby ustawić bieżący element listy podczas iteracji. Będziesz musiał użyćList#listIterator()
zamiastList#iterator()
zainicjować zmienną pętli (która oczywiście musiałaby zostać zadeklarowana jakoListIterator
a nieIterator
).źródło
e = iterator.next()
a, toe = somethingElse
po prostue
zmieniam, do którego obiektu się odnosi, zamiast zmieniać rzeczywisty sklep, z któregoiterator.next()
pobrałem wartość.e
nie zmieniało tego, co jest na liście; musisz zadzwonićlist.set(index, thing)
. Można zmienić zawartość ze
(na przykłade.setSomething(newValue)
), ale co do zmiany element jest przechowywany w wykazie, jak jesteś iteracja go, trzeba trzymać się z iteracji oparte na indeksie.e
musiałbym wywołać jedną ze
metod, ponieważ przypisanie po prostu zmienia wskaźnik (wybacz moje C).Integer
iString
, nie byłoby możliwe zmienianie zawartości za pomocą metodfor-each
lubIterator
metod - musiałby manipulować samym obiektem listy, aby zastąpić elementy. Czy to prawda?Przykład każdego rodzaju wymienionego w pytaniu:
ListIterationExample.java
źródło
Pętla podstawowa nie jest zalecana, ponieważ nie znasz implementacji listy.
Jeśli była to lista połączona, każde połączenie z
będzie iterować po liście, powodując złożoność czasową N ^ 2.
źródło
Iteracja w stylu JDK8:
źródło
W Javie 8 mamy wiele sposobów na iterację klas kolekcji.
Korzystanie z Iterable forEach
Kolekcje, które implementują
Iterable
(na przykład wszystkie listy) mają terazforEach
metodę. Możemy użyć referencji metod wprowadzonych w Javie 8.Korzystanie ze strumieni dla forEach i forEachOrders
Możemy również iterować listę, używając Stream jako:
Powinny wolimy
forEachOrdered
ponadforEach
ponieważ zachowanieforEach
jest wyraźnie niedeterministyczny gdzie jakforEachOrdered
wykonuje czynność dla każdego elementu tego strumienia, w celu napotkać strumienia jeśli strumień ma określony porządek spotkania. ForEach nie gwarantuje, że zamówienie zostanie zachowane.Zaletą strumieni jest to, że możemy również korzystać z równoległych strumieni tam, gdzie jest to właściwe. Jeśli celem jest jedynie wydrukowanie elementów niezależnie od zamówienia, możemy użyć strumienia równoległego jako:
źródło
Nie wiem, co uważasz za patologiczne, ale pozwól, że przedstawię alternatywy, których wcześniej nie widziałeś:
Lub jego wersja rekurencyjna:
Również rekursywna wersja klasycznego
for(int i=0...
:Wspominam o nich, ponieważ jesteś „nieco nowy w Javie” i może to być interesujące.
źródło
while(!copyList.isEmpty()){ E e = copyList.remove(0); ... }
. To jest bardziej skuteczne niż pierwsza wersja;).Możesz używać forEach od Java 8:
źródło
Do wyszukiwania wstecznego należy użyć:
Jeśli chcesz poznać pozycję, użyj iteratora.previousIndex (). Pomaga również napisać wewnętrzną pętlę, która porównuje dwie pozycje na liście (iteratory nie są równe).
źródło
Tak, wymienionych jest wiele alternatyw. Najłatwiejszym i najczystszym byłoby użycie ulepszonego
for
wyrażenia, jak poniżej. JestExpression
to pewnego rodzaju, który jest iterowalny.Na przykład, aby iterować przez listę <String> id, możemy po prostu tak,
źródło
W
java 8
można użyćList.forEach()
metody zlambda expression
do iteracji po liście.źródło
eugene82
odpowiedzi ii_am_zero
odpowiedzi ?Zawsze możesz zamienić pierwszy i trzeci przykład za pomocą pętli while i trochę więcej kodu. Daje to korzyść z możliwości korzystania z do-while:
Oczywiście tego rodzaju rzeczy mogą powodować wyjątek NullPointerException, jeśli list.size () zwraca 0, ponieważ zawsze jest wykonywane przynajmniej raz. Można to naprawić, sprawdzając, czy element jest pusty przed użyciem jego atrybutów / metod. Mimo to korzystanie z pętli for jest o wiele prostsze i łatwiejsze
źródło