Skip to main content

[Java] Making First Collection More Collection-like - Iterable

· 2 min read

Overview

// Java Collection that implements Iterable.
public interface Collection<E> extends Iterable<E>

First-class collections are a very useful way to handle objects. However, despite the name "first-class collection," it only holds Collection as a field and is not actually a Collection, so you cannot use the various methods provided by Collection. In this article, we introduce a way to make first-class collections more like a real Collection using Iterable.

Let's look at a simple example.

Example

@Value
public class LottoNumber {
int value;

public static LottoNumber create(int value) {
return new LottoNumber(value);
}
}
public class LottoNumbers {

private final List<LottoNumber> lottoNumbers;

private LottoNumbers(List<LottoNumber> lottoNumbers) {
this.lottoNumbers = lottoNumbers;
}

public static LottoNumbers create(LottoNumber... numbers) {
return new LottoNumbers(List.of(numbers));
}

// Delegates isEmpty() method to use List's methods.
public boolean isEmpty() {
return lottoNumbers.isEmpty();
}
}

LottoNumbers is a first-class collection that holds LottoNumber as a list. To check if the list is empty, we have implemented isEmpty().

Let's write a simple test for isEmpty().

@Test
void isEmpty() {
LottoNumber lottoNumber = LottoNumber.create(7);
LottoNumbers lottoNumbers = LottoNumbers.create(lottoNumber);

assertThat(lottoNumbers.isEmpty()).isFalse();
}

It's not bad, but AssertJ provides various methods to test collections.

  • has..
  • contains...
  • isEmpty()

You cannot use these convenient assert methods with first-class collections because they do not have access to them due to not being a Collection.

More precisely, you cannot use them because you cannot iterate over the elements without iterator(). To use iterator(), you just need to implement Iterable.

The implementation is very simple.

public class LottoNumbers implements Iterable<LottoNumber> {

//...

@Override
public Iterator<LottoNumber> iterator() {
return lottoNumbers.iterator();
}
}

Since first-class collections already have Collection, you can simply return it just like you delegated isEmpty().

@Test
void isEmpty_iterable() {
LottoNumber lottoNumber = LottoNumber.create(7);
LottoNumbers lottoNumbers = LottoNumbers.create(lottoNumber);

assertThat(lottoNumbers).containsExactly(lottoNumber);
assertThat(lottoNumbers).isNotEmpty();
assertThat(lottoNumbers).hasSize(1);
}

Now you can use various test methods.

Not only in tests but also in functionality implementation, you can conveniently use it.

for (LottoNumber lottoNumber : lottoNumbers) {
System.out.println("lottoNumber: " + lottoNumber);
}

This is possible because the for loop uses iterator().

Conclusion

By implementing Iterable, you can use much richer functionality. The implementation is not difficult, and it is close to extending functionality, so if you have a first-class collection, actively utilize Iterable.