Skip to content

Iterator

When using collections such as List orSet, we often want to perform certain operations (other than add or remove) on all of their items. Such collections store data in various ways, e.g. in an array. However, in order not to break the SOLID rules, we should not give users the possibility to modify it directly.

To provide access to the elements of the collection, we use the Iterator pattern, which enables iteration over the collection. Such an iterator should be able to:

  • get the next item
  • check, if there is a next item

Examples

When creating an object that we want to iterate over, we have the following options:

  • we can use the Iterable interface which returns an iterator. This interface is useful when you use existing collections that give access to existing iterators (e.g., using the iterator() method).
  • we can implement the Iterator interface and write implementations of thehasNext and next methods.

The following example uses the Iterable interface (i.e. does not implement the pattern directly):

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

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

@Data
@NoArgsConstructor
@AllArgsConstructor
public class JavaFile implements Iterable<String> {
  private String fileName;
  private String className;
  private List<String> linesContent = new ArrayList<>();

  public void addLine(final String line) {
    linesContent.add(line);
  }

  @Override
  public String toString() {
    return "JavaFile{" +
        "fileName='" + fileName + '\'' +
        ", className='" + className + '\'' +
        ", linesContent=" + String.join("\n", linesContent) +
        '}';
  }

  @Override
  public Iterator<String> iterator() {
    return linesContent.iterator();
  }
}

The next example uses both of these interfaces:

  • the ParkingLot class implements the Iterable interface and has the ability to get CarIterator, which allows you to sequentially fetch Car objects from an array inside the ParkingLot class
  • the CarIterator class implements the Iterator interface and has the ability to get another element.
public interface Car {
  String getVehicleInfo();
}
import lombok.ToString;

@ToString
public class SimpleCar implements Car {

  private static int index = 0;
  private final String info;

  public SimpleCar() {
    info = "Mazda 6 with id " + ++index;
  }

  @Override
  public String getVehicleInfo() {
    return info;
  }
}
import java.util.Iterator;

public class ParkingLot implements Iterable<Car> {

  private static final int INITIAL_CAPACITY = 5;
  private int indexToAdd = 0;

  private Car[] cars = new Car[INITIAL_CAPACITY];

  public void add(final Car car) {
    if (indexToAdd == cars.length) {
      Car[] biggerCars = new Car[2 * cars.length];
      for (int idx = 0; idx < cars.length; idx++) {
        biggerCars[idx] = cars[idx];
      }
      cars = biggerCars;
    } else {
      cars[indexToAdd++] = car;
    }
  }

  @Override
  public Iterator<Car> iterator() {
    return new CarIterator();
  }

  public class CarIterator implements Iterator<Car> {

    private int index = 0;

    @Override
    public boolean hasNext() {
      return index < cars.length && cars[index] != null;
    }

    @Override
    public Car next() {
      return cars[index++];
    }
  }
}
import java.util.Iterator;

public class ParkingLotUsage {
  public static void main(String[] args) {
    final ParkingLot parkingLot = new ParkingLot();

    for (int idx = 0; idx < 12; idx++) {
      parkingLot.add(new SimpleCar());
    }

    final Iterator<Car> iterator = parkingLot.iterator();
    while (iterator.hasNext()) {
      final Car car = iterator.next();
      System.out.println(car);
    }
  }
}

/* Output:
SimpleCar(info=Mazda 6 with id 1)
SimpleCar(info=Mazda 6 with id 2)
SimpleCar(info=Mazda 6 with id 3)
SimpleCar(info=Mazda 6 with id 4)
SimpleCar(info=Mazda 6 with id 5)
SimpleCar(info=Mazda 6 with id 7)
SimpleCar(info=Mazda 6 with id 8)
SimpleCar(info=Mazda 6 with id 9)
SimpleCar(info=Mazda 6 with id 10)
SimpleCar(info=Mazda 6 with id 11)
*/

The Iterator pattern is useful when we need to implement our own collections (which, contrary to popular belief, is not a rare case) and be able to perform operations on their elements. For this purpose, we usually use the already existing Iterable and Iterator interfaces.