Intro
My idea this time was to be able to write something like this:
List<Integer> list1 = List.of(1, 2, 3);
List<Integer> list2 = List.of(4, 5);
for (Integer i : new CompoundListIterable<>(list1, list2)) {
System.out.println(i);
}
prints
1
2
3
4
5
Code
io.github.coderodde.util.CompoundListIterable.java:
package io.github.coderodde.util;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Objects;
/**
* This class combines several lists and exposes them as an iterable over them.
*
* @param <E> the list element type.
*
* @author Rodion "rodde" Efremov
* @version 1.0.0 (Dec 23, 2025)
* @since 1.0.0 (Dec 23, 2025)
*/
public class CompoundListIterable<E> implements Iterable<E> {
/**
* The actual lists to iterate.
*/
private final List<List<E>> lists;
/**
* The sum of all list sizes.
*/
private final int compoundLength;
public CompoundListIterable(List<E>... lists) {
checkConstructorInput(lists);
this.lists = List.of(lists);
this.compoundLength = computeCompoundLength(lists);
}
@Override
public Iterator<E> iterator() {
return new CompoundListIterator();
}
/**
* This inner class implements the actual iterator over compound lists.
*/
private class CompoundListIterator implements Iterator<E> {
/**
* The local index into the current list under iteration.
*/
private int localListIndex = 0;
/**
* The index of the current list under iteration.
*/
private int currentListIndex = 0;
/**
* Caches the number of iterated elements.
*/
private int iterated = 0;
@Override
public boolean hasNext() {
return iterated < compoundLength;
}
@Override
public E next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
List<E> currentList = lists.get(currentListIndex);
// Omit empty lists:
while (currentList.isEmpty()) {
currentList = lists.get(++currentListIndex);
localListIndex = 0;
}
int myLocalListIndex = localListIndex;
++localListIndex;
++iterated;
if (localListIndex == currentList.size()) {
// Once here, move to the next list:
localListIndex = 0;
currentListIndex++;
}
return currentList.get(myLocalListIndex);
}
}
/**
* Checks that none of the input lists are {@code null}.
*
* @param <E> the list element type.
* @param lists the lists to check.
*/
private static <E> void checkConstructorInput(List<E>[] lists) {
int index = 0;
for (List<E> list : lists) {
Objects.requireNonNull(
list,
String.format("Input list at index %d is null",
index));
++index;
}
}
/**
* Computes the sum of all the lists' sizes.
*
* @param <E> the list element type.
* @param lists the lists whose sizes to sum.
* @return the total size of all the input lists.
*/
private static <E> int computeCompoundLength(List<E>[] lists) {
int length = 0;
for (List<E> list : lists) {
length += list.size();
}
return length;
}
}
io.github.coderodde.util.CompoundListIterableTest.java:
package io.github.coderodde.util;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import org.junit.Test;
import static org.junit.Assert.*;
public class CompoundListIterableTest {
@Test
public void emptyListList() {
Iterator<Integer> it = new CompoundListIterable<Integer>().iterator();
assertFalse(it.hasNext());
}
@Test
public void emptyTwoLists() {
Iterator<Integer> it =
new CompoundListIterable<Integer>(List.of(),
List.of()).iterator();
assertFalse(it.hasNext());
}
@Test
public void twoLists() {
List<Integer> list1 = List.of(1, 2, 3);
List<Integer> list2 = List.of(4, 5);
Iterator<Integer> it =
new CompoundListIterable<>(list1, list2).iterator();
for (int i = 1; i < 6; ++i) {
assertEquals(Integer.valueOf(i), it.next());
}
}
@Test
public void threeListsMiddleIsEmpty() {
List<Integer> list1 = List.of(1, 2, 3);
List<Integer> list2 = List.of();
List<Integer> list3 = List.of(4, 5);
Iterator<Integer> it =
new CompoundListIterable<>(list1, list2, list3).iterator();
for (int i = 1; i < 6; ++i) {
assertEquals(Integer.valueOf(i), it.next());
}
}
@Test
public void threeListsLeftIsEmpty() {
List<Integer> list1 = List.of();
List<Integer> list2 = List.of(1, 2, 3);
List<Integer> list3 = List.of(4, 5);
Iterator<Integer> it =
new CompoundListIterable<>(list1, list2, list3).iterator();
for (int i = 1; i < 6; ++i) {
assertEquals(Integer.valueOf(i), it.next());
}
}
@Test
public void threeListsRightIsEmpty() {
List<Integer> list1 = List.of();
List<Integer> list2 = List.of(1, 2, 3);
List<Integer> list3 = List.of(4, 5);
Iterator<Integer> it =
new CompoundListIterable<>(list1, list2, list3).iterator();
for (int i = 1; i < 6; ++i) {
assertEquals(Integer.valueOf(i), it.next());
}
}
@Test
public void largerIterable() {
List<Integer> list1 = List.of();
List<Integer> list2 = List.of(1, 2, 3);
List<Integer> list3 = List.of();
List<Integer> list4 = List.of();
List<Integer> list5 = List.of();
List<Integer> list6 = List.of(4, 5, 6, 7);
List<Integer> list7 = List.of();
List<Integer> list8 = List.of();
Iterator<Integer> it =
new CompoundListIterable<>(list1,
list2,
list3,
list4,
list5,
list6,
list7,
list8).iterator();
for (int i = 1; i <= 7; ++i) {
assertEquals(Integer.valueOf(i), it.next());
}
}
@Test(expected = NoSuchElementException.class)
public void throwsOnNextAfterLastElementIterated() {
Iterator<Integer> it =
new CompoundListIterable<>(List.of(1),
List.of(),
List.of()).iterator();
it.next();
it.next();
}
}
(Jacoco reports 100% test coverage.)
Critique request
As always, I am eager to receive any constructive comments.