Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 5.3
Excerpt

java.util.Iterator og java.utillang.Iterable er to grensesnitt for å henholdsvis opprette en iterator og sikre at en klasse har en iterator.som brukes i forbindelse med iterasjon, typisk å gå gjennom elementene i en samling objekter.

Iterator-basert løkke

Mange objekter inneholder samlinger av elementer. Eksempelvis vil et Familie-objekt inneholde en samling av personer, mens et Bibliotek-objekt vil ha en samling av bøker. En IteratorEn iterator er et objekt som lar deg iterere, altså gå gjennom alle , elementene til et annet objekt. En BibliotekIterator vil kunne gå gjennom alle bøkene i et Bibliotek-objekt, men hvordan dette gjøres vil avhenge av av hvordan BibliotekIteratoren er laget. For eksempel, skal man gå gjennom alle bøkene i alfabetisk rekkefølge, eller skal man gå gjennom bøkene gruppert på forfatter?. (Når vi sier noe er en iterator, så mener vi egentlig at det er en instans av Iterator-grensesnittet.) Iteratoren husker hvor langt en er kommet i rekken av elementer og underveis kan den 1) si om det er flere elementer igjen med hasNext()-metoden og 2) levere neste element og gå ett steg videre med next()-metoden. Logikken i en iterator-basert løkke blir som følger:

Code Block
// antar her at samlingen med elementer er String-objekter
Iterator<String> iterator = ...
// så lenge det er flere elementer igjen
while (iterator.hasNext()) {
	// hent ut neste element og (implisitt) ta et steg videre
	String element = iterator.next();
	// gjør noe med elementet som element-variablen refererer til her
	...
}

Her er det viktig å merke seg to ting:

  1. Det er ikke lov til (det gjøres på egen risiko) å kalle next() uten først å ha sjekket om det er flere elementer med hasNext().
  2. Når en har gått gjennom alle elementene, er iteratoren brukt opp og kan ikke restartes.

Fordelen med en Iterator er at løkke-koden blir den samme uavhengig av hva slags data-kilde (altså objektet som holder på elementene) en har og hvordan den er implementert. Så lenge en har en Iterator-implementasjon som passer, så tar den seg av detaljene om hvordan 1) huske hvor langt en er kommet og 2) hente ut neste element.

java.util.Iterator-grensesnittet

Iterator-grensesnittet er som Collection-klassene, spesialisert til typen til elementene. En bruker altså ikke Iterator alene i deklarasjoner, men Iterator<element-type>, hvor <element-type> er den spesifikk typen objekter en itererer over, f.eks. String, Person eller Card.

Iterator-grensesnittet inneholder følgende tre grensesnittet inneholder 3 metoder:

  • boolean hasNext() - returnerer true hvis det er flere elementer igjen av iterasjonenav sekvensen av elementer og false ellers. Denne metoden kan kalles mange ganger uten at det endrer hvor langt en er kommet.
  • <type> next() - returnerer neste element i iterasjonensekvensen og tar implisitt et steg videre. Returtypen er typen som Iterator-implementasjonen er spesialisert til. Denne metoden bør bare kalles dersom hasNext() returnerer true, hvis den kalles når hasNext() returnerer false vil den utløse NoSuchElementException.
  • void remove() - fjerner det siste elementet som ble returnert av iterasjonen (siden metoden er void, er dette "frivillig").

En Iterator må spesifisere hva slags elementtype den skal iterere over. Dette gjøres ved å implementere Iterator<type>, hvor type er en klasse, f.eks. CardPerson eller Book.

  • next() fra den underliggende data-kilden, dersom den støtter dette. Denne metoden er frivillig å implementere, og dersom den ikke er støttet så skal den utløse UnsupportedOperationException.

Datakilder og Iterator-implementasjoner

I kode-eksemplet over er uttrykket som fremskaffer iteratoren utelatt, så hvor kommer så iteratorene fra? Siden det vanligvis er en tett kobling mellom implementasjonen av data-kilden og den tilhørende iteratoren, er det vanlig at det nettopp er datakilden som kan levere en iterator. F.eks. har begge de to standard List-implementasjonene ArrayList og LinkedList en iterator()-metode som returnerer en Iterator-implementasjon for sine data. Iteratoren for en ArrayList vil være ulik iteratoren for en LinkedList, men oppførselen vil være den samme: Den lar deg gå gjennom elementene med next(), så lenge hasNext() returnerer true. Faktisk er det å kunne levere en iterator så viktig for samlinger av objekter, at alle Collection-implementasjoner har en slik metode, og (siden List utvider Collection) dermed også alle List-implementasjoner. Dette betyr at alle Collection-objekter, støtter iterator-basert iterasjon, slik at vi hver gang vi trenger å løpe gjennom elementene i en Collection, kan bruke en iterator.

java.lang.Iterable-grensesnittet

Den vanlige måten å gå gjennom en liste med elementer er med kode som den under til venstre. Dette kalles en for-each-løkke, fordi den går gjennom hvert element i lista. Dette er egentlig spesial-syntaks for iterator-basert iterasjon, det er bare det at du aldri ser iteratoren. Funksjonelt sett er for-each-løkka til venstre ekvivalent med den Iterator-baserte løkka under til høyre. Det er egentlig en smaksak hvilken en bruker, men den venstre varianten er å foretrekke fordi den både er enklere å skrive og lese.

Iterasjon med for-each-løkkeIterasjon med eksplisitt Iterator

...

Code Block
languagejava
// 

...

 

...

gjennom 

...

Iterable-grensesnittet

Når et objekt implementerer Iterable-grensesnittet sikrer man at det er mulig å iterere over objektet. Et Iterable<type>-objekt trenger kun å implementere én metode:

  • Iterator<type> iterator() - returnerer en Iterator<type> for å iterere over objektet.

Under følger et eksempel på Library-klassen fra eksempelet over, som implementerer Iterable<Book>:

Code Block
languagejava
import java.util.Iterator;
 
public class Library implements Iterable<Book> {
	// Tilstander og andre metoder i klassen er ikke definert her
	
	public Iterator<Book> iterator() {
		return new LibraryIterator(this);
	}
}

I mange tilfeller bruker vi ArrayList eller andre typer lister som allerede implementerer Iterator-grensesnittet. Dette kan vi utnytte i nye klasser:

stringListe
for (String s : stringListe) {
	// gjør noe med s her
	...
}

 

 

Code Block
languagejava
// få en iterator fra stringListe 
Iterator<String> stringIterator = stringListe.iterator();
// bruk hasNext og next for å gå gjennom lista
while (stringIterator.hasNext()) {
	String s : stringIterator.next(); 
	// gjør noe med s her
	...
}

Hvis en tenker over det, så er det nettopp iterator()-metoden, som er nøkkelen til at for-each-løkka virker. Det holder at stringListe i kode-eksemplet har en slik metode, for at den skal kunne "omskrives" til koden til høyre som den tilsvarer. Denne koblingen mellom for-each-løkka og iterator()-metoden er ikke tilfeldig, og for å gjøre koblingen eksplisitt og mulig å utnytte for klasser utenfor Collection-rammeverket, så er iterator()-metoden definert i et eget grensesnitt ved navn Iterable (i java.lang-pakken, så en slipper egen import-setning). Det er altså fordi en ArrayList implementerer Iterable at en for-each-løkke med en ArrayList virker! Og ArrayList implementerer Iterable fordi den implementerer List, og List utvider Collection som utvider Iterable. Denne koblingen mellom ArrayList og Iterable er illustrert under med et klassediagram:

PlantUML Macro
interface "Iterable<T>" as iterable {
	Iterator<T> iterator()
}
interface "Collection<T>" as collection
interface "List<T>" as list
class "ArrayList<T>" as arraylist
class "LinkedList<T>" as linkedlist

iterable <|-right- collection
collection <|-right- list
list <|.down. arraylist
list <|.down. linkedlist

Som vi ser er også Iterable spesialisert til element-typen. Så dersom en har en List<String> så har en implisitt en Iterable<String>, som har en iterator()-metode som returnerer en Iterator<String>, som har en next()-metode som returnerer en String.

Siden Iterable er et vanlig grensesnitt, så kan våre egne klasser utvide den og utnytte den kompakte for-each-syntaksen. Anta f.eks. at en har en Library-klasse, som bruker en ArrayList til å holde Book-objekter. Kode for en slik klasse er vist under til venstre. Dersom denne klassen også implementerer Iterable<Book> så kan en bruke for-each-løkka for å gå gjennom Book-objektene i et Library-objekt. Koden og klassediagram for denne utvidete Library-klassen er vist i midten og for-each-løkka er vist til høyre.

...

languagejava

...

Klasse som bruker ArrayListKlasse som også implementerer IterableKlassediagramfor-each-løkke
Code Block
languagejava
public class Library {

	private Collection<Book> books = new ArrayList<Book>();

	public void addBook(Book book) {
		books.add(book);
	}
	public void removeBook(Book book) {
		books.remove(book);
	}
}
Code Block
languagejava
public class Library implements Iterable<Book> {

	

...

... books

...

-feltet og add- og 

...

remove-metodene 

...

her ...

	// fra Iterable<Book>
	public Iterator<Book> iterator() {

...


		return books.iterator();
	}
}

Fordeler med Iterator / Iterable

Når et objekt implementerer Iterable<type> kan man bruke for-løkker av denne typen:

Code Block
languagejava
for (TypeObject element : IterableObject) {
	// Her går vi gjennom alle elementer i IterableObject-objektet, som inneholder TypeObject-objekter
}

...

 

 

PlantUML Macro
interface "Iterable<Book>" as iterable {
	Iterator<Book> iterator()
}
class Library

iterable <|.. Library 
Code Block
// lag en Library-instans
Library library = new Library();
// legg til noen bøker
library.addBook(new Book(...));
library.addBook(new Book(...));

// gå gjennom bøkene
for (Book book : library) {
	// gjør noe med book her
	...
}
Ferdig
90