Spôsoby iterácie nad zoznamom v jazyku Java
Ako nováčik v jazyku Java sa snažím oboznámiť so všetkými spôsobmi (alebo aspoň s tými nepatologickými), ktorými možno iterovať cez zoznam (alebo možno iné kolekcie), a s výhodami či nevýhodami každého z nich.
Pri objekte List list poznám nasledujúce spôsoby prechádzania všetkých prvkov:
(samozrejme, existujú aj ekvivalentné slučky while
/ do while
)
// 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
// ...
}
Poznámka: Ako upozornil @amarseillan, tento tvar je zlou voľbou
pre iteráciu cez List
, pretože skutočná implementácia
metódy get
nemusí byť taká efektívna ako pri použití Iterátora
.
Napríklad implementácie LinkedList
musia prechádzať všetky
prvkami predchádzajúcimi prvku i, aby sa získal i-ty prvok.
Vo vyššie uvedenom príklade neexistuje spôsob, ako by implementácia List
"uložiť svoje miesto", aby boli budúce iterácie efektívnejšie.
Pre ArrayList
na tom v skutočnosti nezáleží, pretože zložitosť/náklady na get
sú konštantné (O(1)), zatiaľ čo pre LinkedList
sú úmerné veľkosti zoznamu (O(n)).
Viac informácií o výpočtovej zložitosti zabudovaných implementácií Collections
nájdete v táto otázka.
Vylepšená slučka for (pekne vysvetlené v tejto otázke)
for (E element : list) {
// 1 - can call methods of element
// ...
}
Iterátor
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][list-iterátor]
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
// ...
}
Funkčná Java
list.stream().map(e -> e + 1); // Can apply a transformation function for e
Iterable.forEach, Stream.forEach, ...
(Metóda mapovania z Java 8's Stream API (pozri odpoveď @i_am_zero's).)
V Jave 8 majú triedy kolekcií, ktoré implementujú Iterable
(napríklad všetky Listy
), teraz metódu forEach
, ktorú možno použiť namiesto vyššie demonštrovaného príkazu for-loop. (Tu je iná otázka, ktorá poskytuje dobré porovnanie.)
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).
Aké sú ďalšie spôsoby, ak nejaké existujú?
(BTW, môj záujem vôbec nevyplýva z túžby optimalizovať výkon; chcem len vedieť, aké formy mám ako vývojár k dispozícii.)
Príklad každého druhu uvedeného v otázke:
ListIterationExample.java
Neviem, čo považujete za patologické, ale dovoľte mi poskytnúť niekoľko alternatív, ktoré ste doteraz nemohli vidieť:
Alebo jeho rekurzívna verzia:
Tiež rekurzívna verzia klasického
for(int i=0...
:Spomínam ich preto, lebo ste "trochu nováčik v Jave" a toto by mohlo byť zaujímavé.
Prvý a tretí príklad môžete vždy vymeniť za cyklus while a trochu viac kódu. Tým získate výhodu, že budete môcť používať do-while:
Samozrejme, takáto vec môže spôsobiť výnimku NullPointerException, ak list.size() vráti 0, pretože sa vždy vykoná aspoň raz. Toto sa dá vyriešiť testovaním, či je prvok nulový pred použitím jeho atribútov/metód. Napriek tomu je oveľa jednoduchšie a ľahšie použiť cyklus for