Iterable


Iterable versus Iterator

  • Et Iterator -objekt kan «bla gjennom» en samling elementer én gang.
  • Et Iterable -objekt har en metode for å lage nye Iterator-objekter. Et objekt som er Iterable kan man derfor bla igjennom flere ganger.

Grensesnittet Iterable er egentlig svært enkelt, og inneholder i bunn og grunn én metode:

public interface Iterable<T> {
  /**
   * Returns an iterator over elements of type {@code T}.
   *
   * @return an Iterator.
   */
  Iterator<T> iterator();
}

Det eneste kravet for å være en Iterable er altså at man har en iterator -metode som lager et nytt Iterator -objekt.

Grensesnittet Iterator er også et grensesnitt:

public interface Iterator<E> {
  /**
   * Returns {@code true} if the iteration has more elements.
   * (In other words, returns {@code true} if {@link #next} would
   * return an element rather than throwing an exception.)
   *
   * @return {@code true} if the iteration has more elements
   */
  boolean hasNext();

  /**
   * Returns the next element in the iteration.
   *
   * @return the next element in the iteration
   * @throws NoSuchElementException if the iteration has no more elements
   */
  E next();
}

Se gjennom elementene én gang med et Iterator-objekt

Man kan benytte et Iterator-objekt for å hente ut én og én verdi, helt til det er tomt. Når Iterator-objektet er tømt, har det blitt verdiløst, og kan ikke brukes for å produsere verdier mer.

Iterator<Foo> myIterator = /* kode som gav oss Iterator-objekt her */;
while (myIterator.hasNext()) {
  Foo element = myIterator.next();
  // ...
  // gjør noe med elementet her
  // ...
}

// Når vi er ferdig med å hente ut verdier og hasNext() returnerer false,
// har myIterator blitt et tomt og verdiløst objekt, og kan ikke startes på
// nytt (med mindre vi har gjort noe høyst utradisjonelt i Iterator-koden)

Foreach-løkker og Iterable

En foreach-løkke fungerer med alle typer som arver Iterable -grensesnittet. For eksempel er List en utvidelse av Iterable – en variabel med typen List<Foo> kan derfor brukes som argument til en parameter med typen Iterable<Foo>:

import java.util.List;
import java.util.ArrayList;

public class Main {
  public static void main(String[] args) {
    List<String> a = new ArrayList<>();
    a.add("foo");
    a.add("bar");
    printTheStringsTwice(a);
  }

  private static void printTheStringsTwice(Iterable<String> a) {
    for (String s : a) {
      System.out.println(s);
    }
    for (String s : a) {
      System.out.println(s);
    }
  }
}

Foreach-løkken er egentlig en snarvei som kan benyttes for å se igjennom elementene i et Iterable -objekt:

// Denne foreach-løkken
for (Foo iterand : myIterableObject) {
  // bla bla
  // bla bla
}

// ... gjør NØYAKTIG det samme som denne while-løkken:
Iterator<Foo> myIterator = myIterableObject.iterator();
while (myIterator.hasNext()) {
  Foo iterand = myIterator.next();
  // bla bla
  // bla bla
}
// (bortsett fra at vi ikke ser myIterator-variabelen i foreach-løkken,
// er de to kodesnuttene helt identiske)

import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;

public class Main {
  public static void main(String[] args) {
    List<String> a = new ArrayList<>();
    a.add("foo");
    a.add("bar");
    printTheStringsTwice1(a); // med foreach-løkke
    printTheStringsTwice2(a); // med Iterator-variabel
  }

  private static void printTheStringsTwice1(Iterable<String> a) {
    for (String s : a) {
      System.out.println(s);
    }
    for (String s : a) {
      System.out.println(s);
    }
  }

  private static void printTheStringsTwice2(Iterable<String> a) {
    Iterator<String> myIterator = a.iterator();
    while (myIterator.hasNext()) {
      String s = myIterator.next();
      System.out.println(s);
    }
    
    // For å se gjennom elementene én gang til, må vi lage et nytt
    // Iterator -objekt (vi kaller på .iterator() én gang til)
    myIterator = a.iterator();
    while (myIterator.hasNext()) {
      String s = myIterator.next();
      System.out.println(s);
    }
  }
}

Enkel implementasjon av Iterable: bruk noen andre sin iterator-metode

Dersom vi har et objekt som primært består av en samling av andre objekter, er det ofte lurt at klassen til dette objektet implementerer Iterable, slik at kan dra nytte av foreach-løkken. Dersom vi internt benytter for eksempel en liste kan iterator-metoden vi må implementere være så enkel som å bare kalle på iterator-metoden til den interne listen.

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/** PiggBank lets you add integers but not remove them */
class PiggyBank implements Iterable<Integer> {
  private List<Integer> coins = new ArrayList<>();

  public void addCoin(Integer coin) {
    this.coins.add(coin);
  }

  @Override
  public Iterator<Integer> iterator() {
    return this.coins.iterator();
  }
}

public class Main {
  public static void main(String[] args) {
    PiggyBank pig = new PiggyBank();
    pig.addCoin(5);
    pig.addCoin(20);

    for (Integer coin : pig) {
      System.out.println(coin);
    }
  }
}

En enkel måte å gjøre en klasse Iterable, er å opprette en liste med de aktuelle elementene i iterable -metoden og til slutt returnere et kall på .iterator() på denne listen. Da slipper vi fullstendig å forholde oss til Iterator -grensesnittet selv. Dette vil som regel ikke straffe deg noe særlig, men det vil bruke mer minne enn nødvendig dersom listen ikke var noe du uansett hadde liggende fra før.

Range: et eksempel på vår egen iterator

Dersom vi vil redusere minnebruken, kan vi skrive vår egen klasse som implementerer Iterator. Dette gjøres som regel som en såkalt «anonym indre klasse», selv om det også er mulig å gjøre det som en vanlig klasse (som åpenbart er enklere å forstå hvis man ikke har lært seg noe om anonyme indre klasser enda).

I eksempelet under ønsker vi å reprodusere python sine range -objekter. Planene er at vi skal kunne skrive kode som kan brukes slik:

// Skriv ut tallene 4, 5, 6, 7, 8 og 9
for (int i : Range.range(4, 10)) {
  System.out.println(i); 
}

For å oppnå dette:

import java.util.Iterator;
import java.util.NoSuchElementException;

class RangeIterator implements Iterator<Integer> {
  private int next;
  private final int stop;
  private final int step;

  public RangeIterator(int next, int stop, int step) {
    this.next = next;
    this.stop = stop;
    this.step = step;
  }

  @Override
  public boolean hasNext() {
    return this.step > 0
        ? this.next < this.stop
        : this.next > this.stop;
  }

  @Override
  public Integer next() {
    if (!this.hasNext()) throw new NoSuchElementException();
    Integer result = this.next;
    this.next += this.step;
    return result;
  }
}

class Range implements Iterable<Integer> {
  
  // Static factory methods
  public static Range range(int stop) {
    return range(0, stop);
  }

  public static Range range(int start, int stop) {
    return range(start, stop, 1);
  }

  public static Range range(int start, int stop, int step) {
    return new Range(start, stop, step);
  }

  // Instance variables
  private final int start;
  private final int stop;
  private final int step;

  // Constructor
  private Range(int start, int stop, int step) {
    this.start = start;
    this.stop = stop;
    this.step = step;
  }

  @Override
  public Iterator<Integer> iterator() {
    return new RangeIterator(this.start, this.stop, this.step);
  }
}

public class Main {
  public static void main(String[] args) {
    for (int i : Range.range(4)) {
      System.out.println(i);
    }
  }
}

import java.util.Iterator;
import java.util.NoSuchElementException;

class Range implements Iterable<Integer> {
  
  // Static methods
  public static Range range(int stop) {
    return range(0, stop);
  }

  public static Range range(int start, int stop) {
    return range(start, stop, 1);
  }

  public static Range range(int start, int stop, int step) {
    return new Range(start, stop, step);
  }

  // Instance variables
  private final int start;
  private final int stop;
  private final int step;

  // Constructor
  private Range(int start, int stop, int step) {
    this.start = start;
    this.stop = stop;
    this.step = step;
  }

  @Override
  public Iterator<Integer> iterator() {
    return new Iterator<Integer>() {
      private int next = Range.this.start;

      @Override
      public boolean hasNext() {
        return Range.this.step > 0
            ? this.next < Range.this.stop
            : this.next > Range.this.stop;
      }

      @Override
      public Integer next() {
        if (!this.hasNext()) throw new NoSuchElementException();
        Integer result = this.next;
        this.next += Range.this.step;
        return result;
      }
    };
  }
}

public class Main {
  public static void main(String[] args) {
    for (int i : Range.range(4)) {
      System.out.println(i);
    }
  }
}