Przeglądamy kolekcję - interfejs Iterable<T>

Od Javy 5.0 wprowadzono pętlę iteracyjną (znaną w innych językach programowania jako pętla foreach) umożliwiającą przeglądnięcie wszystkich elementów należących do danej kolekcji. Pętla ta może być stosowana z tablicami oraz wszystkim klasami, które implementują interfejs Iterable<T>.
Ogólny format pętli iteracyjnej:
for (T wartosc : kolekcja) {
 // dla każdego elementu z kolekcji dostępnego 
 // pod zmienną wartosc typu T
 // gdzie T jest typem generycznym
}

Powyższy zapis jest równoznaczny z uzyskaniem dla danej kolekcji iteratora (obiekt klasy Iterator<T>) oraz wykorzystania metod, które on udostępnia (np. hasNext(), next()).
Iterator<T> iterator = kolekcja.iterator();

while(iterator.hasNext()) {
 T wartosc = iterator.next();
 
 // dla każdego elementu z kolekcji dostępnego 
 // pod zmienną wartosc typu T
 // gdzie T jest typem generycznym
}
Jak utworzyć klasę, która będzie umożliwiała nam przejście przez wszystkie elementy za pomocą czy to pętli iteracyjnej for czy też while? Odpowiedź jest stosunkowo prosta. Musimy zaimplementować w naszej klasie interfejs Iterable<T> ("powiadamia" on o tym, że naszą klasę można przeglądać). Interfejs ten posiada tylko jedną metodę:
public Iterator<T> iterator();
zwracającą referencję do obiektu klasy implementującej interfejs Iterator<T>. Interfejs ten posiada trzy metody:
public boolean hasNext(); // sprawdza czy są jeszcze elementy w kolekcji
public T next();          // zwraca kolejny element
public void remove();     // usuwa kolejny element
Zazwyczaj implementuje się w pełni tylko dwie pierwsze metody. Trzecią zostawia się pustą (ma ona bezpośredni wpływ na zawartość kolekcji i nie ma sensu w ten sposób usuwać elementów, od tego powinny być metody dostępne w samej kolekcji). Zobaczmy prosty przykład wykorzystania interfejsu Iterable<T> na podstawie klasy Lista<T> odzwierciedlającej listę jednostronną jednokierunkową. Najpierw klasa pojedynczego węzła/elementu listy Wezel<T>.
/**
 * Węzeł - element listy 
 * @author kodatnik.blogspot.com
 */
class Wezel<T> {
 // pole przechowujące wartość znajdującą się w węźle
 private T obiekt;
 // referencja do następnego elementu listy
 private Wezel<T> nastepny;
 
 // konstruktor domyślny;
 public Wezel() {
  // wywołanie konstruktora dwuparametrowego)
  this(null, null);
 }
 
 // konstruktor dwuparametrowy
 // wartość oraz referencja do następnego węzła
 public Wezel(T obiekt, Wezel<T> nastepny) {
  this.obiekt = obiekt;
  this.nastepny = nastepny;
 }
  
 // metoda zwraca referencję do następnego węzła
 public Wezel<T> pobierzNastepny() {
  return nastepny;
 }
 
 // metoda zwraca przechowywaną w węźle wartość
 public T pobierzObiekt() {
  return obiekt;
 }
}
oraz klasa główna Lista<T>:
// wykorzystujemy klasę Iterator z pakietu java.util
import java.util.Iterator;

/**
 * Przykład wykorzystania iteratora
 * Lista jednostronna jednokierunkowa
 * @author kodatnik.blogspot.com
 */
class Lista implements Iterable<T> {

 // pole przechowujące referencję do początku listy
 private Wezel<T> poczatek;
 
 // konstruktor bezparametrowy
 public Lista () {
  // ustawiamy początek na null (lista pusta)
  poczatek = null;
 }
 
 // metoda wstawia dane na początek listy
 public void wstawNaPoczatek(T dane) {
  // tworzymy nowy węzeł oraz ustawiamy 
  // zmienną poczatek tak aby go wskazywała
  poczatek = new Wezel<T>(dane, poczatek);
 }
 
 // metoda usuwa element znajdujący się na początku listy
 // oraz zwraca referencję do niego
 public Wezel<T> usunZPoczatku() {
  // zapamiętujemy element z początku listy
  Wezel<T> temp = poczatek;
  // zmieniamy referencje początku listy
  // na następny element (pomijamy pierwszy)
  poczatek = poczatek.pobierzNastepny();
  // zwracamy zapamiętaną referencję
  return temp; 
 }
 
 // metoda zwraca referencję do obiektu klasy
 // implementującej interfejs Iterator<T>
 public Iterator<T> iterator() {
  // tworzymy nowy obiekt wewnętrznej klasy IteratorListy
  // i zwracamy jego referencję
  return new IteratorListy();
 }
 
 // prywatna klasa wewnętrzna implementująca interfejs Iterator<T>
 private class IteratorListy implements Iterator<T> {
  
  // pole przechowujące referencję do pierwszego elementu naszej listy
  private Wezel<T> temp = poczatek;
  
  // metoda zwraca wartość logiczną czy są jeszcze elementy w kolekcji
  public boolean hasNext() {
   return temp != null;
  }
  
  // metoda zwraca wartość elementu przechowywanego w kolejnym węźle
  public T next() {
   // pobieramy wartość (obiekt typu T)
   T obiekt = temp.pobierzObiekt();
   // przechodzimy na następny element listy
   temp = temp.pobierzNastepny();
   // zwracamy wartość
   return obiekt;
  }
  
  // metoda usuwająca element z kolekcji
  public void remove() {
   // ciało metody puste (patrz opis)
  }
 }
}
Zaimplementowaliśmy w klasie interfejs Iterable<T> (metoda iterator()) oraz utworzyliśmy wewnętrzną prywatną klasę IteratorListy implementującą interfejs Iterator<T>. Sprawdźmy działanie naszej klasy:
// wykorzystujemy klasę Iterator z pakietu java.util
import java.util.Iterator;

/**
 * Test listy
 * @author kodatnik.blogspot.com
 */
public class TestIteratora {
  
 public static void main (String[] args) {
 
  // tworzymy pierwszą listę (parametryzujemy typem String)
  Lista<String> listaPierwsza = new Lista<String>();
  
  // dodajemy elementy na początek listy
  listaPierwsza.wstawNaPoczatek("Adam");
  listaPierwsza.wstawNaPoczatek("Marek");
  listaPierwsza.wstawNaPoczatek("Kasia");
  
  // pobieramy iterator z listy
  Iterator<String> iterator = listaPierwsza.iterator();
  
  // dopóki są jeszcze elementy
  while(iterator.hasNext()) {
   // pobieramy je i wyświetlamy je na ekranie
   System.out.println (iterator.next());
  }
  
  // tworzymy drugą listę (parametryzujemy typem Integer)
  Lista<Integer> listaDruga = new Lista<Integer>();
  
  // dodajemy elementy do początek listy
  listaDruga.wstawNaPoczatek(10);
  listaDruga.wstawNaPoczatek(45);
  listaDruga.wstawNaPoczatek(83);
  
  // w pętli iteracyjnej wyświetlamy po kolei wszystkie elementy
  for (Integer wartosc: listaDruga) {
   System.out.println (wartosc);
  }
 }
}
Uruchomiona aplikacja:
Kasia
Marek
Adam
83
45
10
Kiedy korzystać z iteratora? Wtedy gdy nasza klasa przechowuje większą liczbę takich samych elementów, mówiąc inaczej jest kolekcją jakiś elementów. Implementacja interfejsu Iterable<T> znacznie uprości obsługę takiej klasy.

1 Komentarz - Przeglądamy kolekcję - interfejs Iterable<T>

Anonimowy pisze...

Dobry artykuł. Ma w sobie to czego brakuje reszcie czyli użycie generyków. :)

Prześlij komentarz

Możesz użyć niektórych tagów HTML, takich jak <b>, <i>, <u>, <a> Nie spamuj :)

Popularne posty