Koja je razlika između Java iteratora Iterator i Iterable?
NS: ovaj članak ponovo je u drugačijem stilu — uživajte.
Tog dana Xiao Er je otišao na razgovor u Hikvision, a intervjuer, stari Wang, odmah mu je bacio pitanje: koja je razlika između Iterator i Iterable?
Xiao Er je bio veoma zadovoljan, jer je tačno tri dana ranije pročitao taj tekst u knjizi „Ergeov put ka naprednom Javom”, pa je odgovorio potpuno samouvereno.
Sledi sadržaj članka koji je Xiao Er tada pročitao i veoma dobro zapamtio.
U Javi, kada obilazimo List, uglavnom imamo tri načina.
Prvi: for petlja.
for (int i = 0; i < list.size(); i++) {
System.out.print(list.get(i) + ",");
}Drugi: iterator.
Iterator it = list.iterator();
while (it.hasNext()) {
System.out.print(it.next() + ",");
}Treći: for-each.
for (String str : list) {
System.out.print(str + ",");
}Prvi ćemo preskočiti; drugi koristi Iterator, a treći izgleda kao for-each, ali je ispod haube takođe Iterator — pogledajte dekompilovani kod (u nastavku) i biće vam jasno.
Iterator var3 = list.iterator();
while(var3.hasNext()) {
String str = (String)var3.next();
System.out.print(str + ",");
}for-each je samo sintaksni šećer koji nam programerima omogućava da pri obilasku List-a pišemo manje koda — kraće i jasnije.
Iterator je interfejs koji postoji još od JDK-a 1.2 i nastao je kao poboljšanje interfejsa Enumeration:
- omogućava brisanje elemenata (dodatak metoda remove)
- kratica imena metoda je preglednija (u Enumeration-u su to bile hasMoreElements i nextElement, što nije pregledno)
Pogledajmo izvorni kod Iterator-a:
public interface Iterator<E> {
// proverava da li u kolekciji postoji sledeći objekat
boolean hasNext();
// vraća sledeći objekat iz kolekcije i pomeraju pokazivač pristupa za jedno mesto
E next();
// briše objekat koji je vratila metoda next()
default void remove() {
throw new UnsupportedOperationException("remove");
}
}U JDK-u 1.8 interfejs Iterable je dobio metod forEach. On kao parametar prima objekat tipa Consumer i služi da nad svakim elementom kolekcije izvrši zadatu operaciju. Implementacija koristi for-each petlju za obilazak elemenata kolekcije i za svaki element poziva metod accept objekta Consumer, kojim se izvršava zadata operacija.
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}Pri implementaciji se prvo vrši provera parametra action na null; ako je null, baca se NullPointerException. Zatim se for-each petljom obilaze elementi kolekcije i za svaki od njih poziva action.accept(t), kojim se izvršava zadata operacija. Pošto je Iterable osnovni interfejs svih tipova kolekcija u Java okviru kolekcija, ovaj metod mogu koristiti svi tipovi kolekcija koji implementiraju interfejs Iterable.
On nad svakim elementom iz Iterable-a izvršava zadatu operaciju, pri čemu se sama operacija definiše kroz interfejs Consumer koji se poziva putem metode accept.
List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3));
list.forEach(integer -> System.out.println(integer));Napisano malo razumljivije, to izgleda ovako:
List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3));
list.forEach(new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.println(integer);
}
});Ako pažljivo pogledamo „lice i naličje” ArrayList-a ili LinkedList-a, primetićemo da nigde ne nalazimo direktan trag Iterator-a.

Umesto toga, nalazimo Iterable!
public interface Iterable<T> {
Iterator<T> iterator();
}Drugim rečima, u dijagramu odnosa klase List ne koristi Iterator direktno, već preko Iterable kao međukoraka.
Vratimo se sada na drugi način obilaska List-e.
Iterator it = list.iterator();
while (it.hasNext()) {
}Vidimo da se sve lepo uklapa. Za ArrayList, on redefiniše metod iterator iz interfejsa Iterable:
public Iterator<E> iterator() {
return new Itr();
}Objekat Itr koji se vraća je interna klasa koja implementira interfejs Iterator i na svoj način redefiniše metode hasNext, next, remove itd.
/**
* Implementacija ArrayList-ovog iteratora — interna klasa.
*/
private class Itr implements Iterator<E> {
/**
* Pozicija kursora, odnosno indeks sledećeg elementa.
*/
int cursor;
/**
* Indeks prethodnog elementa.
*/
int lastRet = -1;
/**
* Očekivani broj strukturnih izmena.
*/
int expectedModCount = modCount;
/**
* Proverava da li postoji sledeći element.
*
* @return true ako postoji sledeći element, inače false.
*/
public boolean hasNext() {
return cursor != size;
}
/**
* Dohvata sledeći element.
*
* @return sledeći element u listi.
* @throws NoSuchElementException ako nema sledećeg elementa.
*/
@SuppressWarnings("unchecked")
public E next() {
// unutrašnji niz ArrayList objekta
Object[] elementData = ArrayList.this.elementData;
// beleži trenutnu poziciju iteratora
int i = cursor;
if (i >= size) {
throw new NoSuchElementException();
}
// uvećava poziciju kursora za 1, priprema za sledeću iteraciju
cursor = i + 1;
// beleži indeks prethodnog elementa
return (E) elementData[lastRet = i];
}
/**
* Briše poslednji vraćeni element.
* Iterator može obrisati samo element koji je vratila poslednja metoda next.
*
* @throws ConcurrentModificationException ako je struktura liste izmenjena nakon poslednjeg poziva next.
* @throws IllegalStateException ako se remove pozove pre next, ili ako se u istoj iteraciji remove pozove više puta.
*/
public void remove() {
// proverava da li je nakon poslednjeg poziva next bilo strukturnih izmena
if (expectedModCount != modCount) {
throw new ConcurrentModificationException();
}
// ako pre poziva next nije pozvan remove, baca IllegalStateException
if (lastRet < 0) {
throw new IllegalStateException();
}
try {
// poziva metod remove(int index) ArrayList objekta radi brisanja prethodnog elementa
ArrayList.this.remove(lastRet);
// postavlja poziciju kursora na poziciju prethodnog elementa
cursor = lastRet;
// postavlja indeks prethodnog elementa na -1, što znači da ga nema
lastRet = -1;
// ažurira očekivani broj strukturnih izmena
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
}Neko će možda pitati: zašto se ključne metode Iterator-a, hasNext i next, nisu stavile direktno u interfejs Iterable? Zar nije praktičnije koristiti ih ovako?
Iterable it = list.iterator();
while (it.hasNext()) {
}Prema sufiksu engleske reči, „(Iterable) able” ukazuje na to da List podržava iteraciju, dok „(Iterator) tor” ukazuje na to kako se kroz List iterira.
Podrška iteraciji i konkretan način iteracije očigledno se ne smeju mešati, inače nastaje haos. Bolje je da svako ima svoju ulogu.
Razmislite: da smo Iterator i Iterable spojili, da li bi for-each način obilaska List-e išao lako?
U principu, ako List implementira interfejs Iterable, može se obilaziti na for-each način; kako tačno obilazi zavisi od toga kako sama implementira interfejs Iterator.
Map ne može direktno da koristi for-each, jer Map ne implementira interfejs Iterable — tek preko map.entrySet(), map.keySet(), map.values(), koji vraćaju Collection, može se iskoristiti for-each.
Ako pažljivo proučimo izvorni kod LinkedList-a, primetićemo da LinkedList nije direktno redefinisao metod iterator iz interfejsa Iterable — to radi njegova nadklasa AbstractSequentialList.
public Iterator<E> iterator() {
return listIterator();
}LinkedList je redefinisao metod listIterator:
public ListIterator<E> listIterator(int index) {
checkPositionIndex(index);
return new ListItr(index);
}Ovde primećujemo novi iterator ListIterator, koji nasleđuje interfejs Iterator; pri obilasku List-e može početi od proizvoljnog indeksa i podržava dvosmerni obilazak.
public interface ListIterator<E> extends Iterator<E> {
boolean hasNext();
E next();
boolean hasPrevious();
E previous();
}Znamo da kolekcija (Collection) obuhvata ne samo List, već i Set — tako Iterator podržava i List i Set, dok ListIterator podržava samo List.
Neko će možda pitati: zašto se ne natera List da direktno implementira interfejs Iterator, već se to radi preko interne klase?
To je zato što neke List-e mogu imati više načina obilaska — na primer LinkedList pored normalnog (unapred) podržava i obrnuti redosled obilaska — DescendingIterator:
/**
* Implementacija ArrayList-ovog obrnutog iteratora — interna klasa.
*/
private class DescendingIterator implements Iterator<E> {
/**
* Koristi objekat ListItr za obilazak unazad.
*/
private final ListItr itr = new ListItr(size());
/**
* Proverava da li postoji sledeći element.
*
* @return true ako postoji sledeći element, inače false.
*/
public boolean hasNext() {
return itr.hasPrevious();
}
/**
* Dohvata sledeći element.
*
* @return sledeći element u listi.
* @throws NoSuchElementException ako nema sledećeg elementa.
*/
public E next() {
return itr.previous();
}
/**
* Briše poslednji vraćeni element.
* Iterator može obrisati samo element koji je vratila poslednja metoda next.
*
* @throws UnsupportedOperationException ako lista ne podržava brisanje.
* @throws IllegalStateException ako se remove pozove pre next, ili ako se u istoj iteraciji remove pozove više puta.
*/
public void remove() {
itr.remove();
}
}Vidimo da DescendingIterator koristi upravo mogućnost ListIterator-a da obilazi unazad. Može se koristiti na sledeći način:
Iterator it = list.descendingIterator();
while (it.hasNext()) {
}Dobro, o Iterator-u i Iterable-u za sada toliko. Da sumiramo u dve tačke:
- Naučite da razmišljate duboko, malo po malo da raspetljavate koncepte i češće razmišljate zašto se nešto implementira baš tako — mnogi problemi nisu toliko složeni koliko se na prvi pogled čine.
- Ne odustajte pred nedoumicama — to je najbolja prilika za napredak; dok rešavate težak problem, iskopate mnogo povezanih stvari.
