LazyIterable will get back to you later

Donald Raab
5 min readAug 9, 2024

--

Learn about a lesser known lazy Iterable type in Eclipse Collections.

Photo by Priscilla Du Preez 🇨🇦 on Unsplash

Old, Rich, Lazy, and Inexhaustible

The open source Eclipse Collections Java library has a type named LazyIterable that provides a lazy alternative to methods that would otherwise return collection types. LazyIterable is a teenager, has a very feature-rich API, is lazy when possible, and inexhaustible. This means an instance of LazyIterable can be used over and over again.

The Old

LazyIterable has been available publicly since GS Collections 1.0 was released in January 2012. This means LazyIterable was developed and used in internal production applications in Goldman Sachs some time before 2012. LazyIterable is a teenager, which in dog years means it has been around the block a few times.

Eclipse Collections started out as an eager-only library. As use cases rolled in from clients over the years, fused methods like collectIf were added to reduce multiple iterations for common pattern combinations like select + collect, a.k.a. filter + map.

LazyIterable was added to the library so the same useful and familiar methods on RichIterable could be executed both eagerly and lazily.

The Rich

LazyIterable sits below RichIterable in the Eclipse Collections interface hierarchy.

RichIterable is extended by MutableCollection, ImmutableCollection, and LazyIterable types

All MutableCollection and ImmutableCollection types can return a LazyIterable by calling the method named asLazy.

The primitive collection hierarchy also has LazyIterable equivalents for each of the eight Java primitive iterable types.

There are Lazy[Boolean/Byte/Char/Short/Int/Float/Long/Double]Iterable types

These UML class hierarchies show only the breadth of laziness available in the Eclipse Collection library. They do not show the depth. The following Venn Diagram shows the method counts of LazyIterable and Java Stream, along with Collectors and Collectors2.

Non-overloaded Method counts for LazyIterable, Stream, Collectors, and Collectors2

The following table shows the unique and common method names (and counts) between LazyIterable and Java Stream.

The following table shows the unique and common method names (and counts) between LazyIterable and Collectors2. Collectors2 is a utility class in Eclipse Collections that supplies Collector instances that work with the Java Stream collect method.

The following table shows the unique and common method names (and counts) between LazyIterable and Java Collectors.

The intersections between Stream, Collectors, and Collectors2 are less interesting. The only method Stream and Collectors have in common is toList. The only methods that Collectors and Collectors2 have in common are toList, toSet, and toMap. The only methods Stream and Collectors2 have in common are collect and toList. The difference between the method named collect in Eclipse Collections and collect in Java Stream is stark.

To further understand what makes LazyIterable a feature-rich interface, I recommend reading the following blog.

The Lazy and Inexhaustible

The difference between an Iterable and a Stream in Java may be confusing to some. LazyIterable is an Iterable. The expectation of an Iterable is that it is a supplier of an Iterator. While Stream has an iterator method, it is not an Iterable. Stream can only supply this and any other terminal methods safely, once. This has confused plenty of developers over the years.

The following code shows how iterator may be called repeatedly on a LazyIterable, and only callable once on a Stream.

@Test
public void streamAndLazyIterableIteratorComparison()
{
MutableList<Integer> integers = Lists.mutable.of(1, 2, 3, 4, 5);
Stream<Integer> stream = integers.stream();

// Terminal methods may be only used once with a Stream
stream.iterator().forEachRemaining(System.out::print);
Assertions.assertThrows(IllegalStateException.class,
() -> stream.iterator().forEachRemaining(System.out::print));
Assertions.assertThrows(IllegalStateException.class,
() -> stream.iterator().forEachRemaining(System.out::print));

// Prints : 12345

System.out.println();

// Methods on LazyIterable can be used as many times as needed
LazyIterable<Integer> lazyIterable = integers.asLazy();
lazyIterable.iterator().forEachRemaining(System.out::print);
lazyIterable.iterator().forEachRemaining(System.out::print);
lazyIterable.iterator().forEachRemaining(System.out::print);

// Prints : 123451234512345
}

This important difference makes it possible for a LazyIterable to be used safely as both a parameter to and as a return value from a method. Stream is unsafe to use as both a parameter and return value, because it is possible that the Stream has already been exhausted.

There are potential performance implications of passing around LazyIterable, as any processing that is stacked up lazily has to be repeated each time. If performance is an issue, then store the results of processing intermediate results from a LazyIterable into a Collection first.

For developers like myself who are lazy and would get quickly exhausted trying to learn so many methods, there are better ways to group methods in a Class than just alphabetically.

Here are all of the methods in LazyIterable grouped by prefix.

Here all all the methods in LazyIterable grouped by suffix.

Rich interfaces like LazyIterable and Stream require more advanced grouping features in the Java language to help developers more quickly find what they are looking for. This is why I am an advocate for Method Categories being added to the Java language.

To see other ways of breaking Java Stream into smaller chunks, check out the following blog.

Further Reading

I wrote a blog almost seven years ago that shows what it means for LazyIterable to be both lazy and inexhaustible.

I also explain in the following blog how Eclipse Collections evolved from eager, to fused, to lazy over the years.

If the differences between eager and lazy are unclear, the following blog might help. I recommend learning and using eager iteration patterns before learning and using lazy iteration patterns. Eager is simpler to use and understand. Lazy is sometimes, but not always, a performance optimization. Standard rules of premature performance optimization apply. Go simple unless you can prove you will go faster.

Thank you!

I hope you find this blog useful. The differences between LazyIterable and Stream are subtle but important. I believe these differences are not not well known. As often the case with Eclipse Collections, there is not an either/or question when it comes to using LazyIterable or Java Stream. Both can be used for good effect with Eclipse Collections types. Use whatever works best for the problems you are trying to solve.

Thanks for reading!

I am the creator of and committer for the Eclipse Collections OSS project, which is managed at the Eclipse Foundation. Eclipse Collections is open for contributions.

--

--

Donald Raab

Java Champion. Creator of the Eclipse Collections OSS Java library (https://github.com/eclipse/eclipse-collections). Inspired by Smalltalk. Opinions are my own.