Better Programming

Advice for programmers.

Follow publication

Rich, Lazy, Mutable, and Immutable Interfaces in Eclipse Collections

Learn about Java collection interfaces with intention revealing names.

Donald Raab
Better Programming
Published in
11 min readJul 28, 2023

A quick guide to reading this blog

I wrote this blog to help explain the design of the Eclipse Collections interface hierarchies to folks who are new to the library and might find the large number of interfaces disorienting to learn by browsing through code and/or Javadoc. This blog should also be a good reference for folks that understand the design of Eclipse Collections, but occasionally want a quick link to find, validate, or explain some things.

The rest of this blog will explain the relationship between Rich, Lazy, Mutable, and Immutable types in Eclipse Collections. The end of this blog has a reference section with a list of all the unique methods that show why Eclipse Collection is a “rich” collections library for Java. Click the link to skip there if that is what you are looking for.

Enjoy!

Everything (almost) extends Iterable

All object containers in Eclipse Collections, with the exception of Multimap types, extend java.lang.Iterable. This design decision makes it possible to use any Eclipse Collections interface with a Java 5 style for each loop. This provides the most basic and flexible form of external iteration you expect from a Java Iterable.

All container types in Eclipse Collections, including Multimap types, favor providing internal iterators like forEach that take a Functional Interface as a parameter that can be expressed as a lambda or method reference. It is not necessary to extend java.lang.Iterable to support internal iterators. As of Java 8, even java.lang.Iterable added a default forEach method to make internal iterators possible on JDK types.

Eclipse Collections has an interface named InteralIterable that inherits directly from java.lang.Iterable. The purpose of InternalIterable is to provide internal iterators like forEach to all Eclipse Collections types. There are also two other methods defined on InternalIterable named forEachWith and forEachWithIndex.

The forEachWithIndex method is marked as deprecated because with unordered collections, an index is not meaningful and winds up being just an iteration counter instead. If you are looking for something to have an iteration counter over an unordered type, then forEachWithIndex (while poorly named) might be useful to you.

The following code shows how a for loop, forEach and forEachWith method can be used with any InternalIterable in Eclipse Collections.

@Test
public void forLoopVsForEachVsForEachWith()
{
InternalIterable<String> iterable = Lists.immutable.with("a", "b", "c");

// Java 5 for each loop
for (String each : iterable)
{
this.output(each, String::toUpperCase);
}

// forEach with lambda
iterable.forEach(each -> this.output(each, String::toUpperCase));

// forEachWith with method reference
iterable.forEachWith(this::output, String::toUpperCase);
}

public void output(String each, Function<String, String> function)
{
System.out.print(function.valueOf(each));
}

// Output:
// ABCABCABC

The methods forEach, forEachWith, forEachWithIndex are occasionally useful. There are many more useful methods on the RichIterable interface and its child interfaces LazyIterable, MutableCollection, and ImmutableCollection.

The Child and Grandchildren of InternalIterable

The four “rich” types in Eclipse Collections that extend InternalIterable are RichIterable, LazyIterable, MutableCollection and ImmutableCollection. The hierarchy of these types is shown in the following class diagram.

The “Rich” Interfaces of Eclipse Collections

The interfaces in the preceding diagram communicate their capabilities through the prefix in their names — Rich, Lazy, Mutable, and Immutable. Developers can leverage these names to communicate their intent to developers who use their APIs.

  • Rich — Read-only, serial. Lazy or eager behavior (up to implementation).
  • Lazy — Read-only, serial, lazy behavior for non-terminal methods.
  • Mutable — Serial, eager, with mutating methods for growth
  • Immutable — Serial, eager, with non-mutating methods for growth

What does the “Rich” in RichIterable mean?

The design philosophy behind RichIterable favored defining useful internal iteration patterns that were seen in production code directly on the interface. There are many useful iteration patterns we saw in production code over the years, so we named them as best we could, and added them as features to the RichIterable interface. We thought the plethora of methods made this a feature-rich interface. This is how we arrived at the name RichIterable for the parent interface for most of the types in Eclipse Collections. RichIterable is an Iterable that is feature-rich.

RichIterable provides many methods that iterate over a container or view and perform some useful algorithms. RichIterable is a serial and read-only interface, so it has no methods that mutate the underlying container. RichIterable can be useful as both a parameter and return type. Methods that employ RichIterable can either accept or return LazyIterable, MutableCollection, or ImmutableCollection types, while only exposing the read-only API. There is no guarantee that methods on RichIterable are lazy or eager, or that the types are mutable or immutable. That decision is left up to the implementation types. If you want something to explicitly be mutable or immutable, then it is better to use the more explicit named types with these prefixes.

How many methods makes a feature-rich interface?

I don’t know if there is a minimum method count for an interface to qualify as feature-rich. I counted 176 methods defined in RichIterable that were not defined in InternalIterable or Iterable. I believe this method count qualifies.

One Hundred Seventy Six methods = Most certainly feature-rich

Click this link or scroll to the bottom of the blog to see the list of unique methods with return types defined on RichIterable<T>. I moved this list to the bottom of the blog because it has 130 unique methods. That is a lot to scroll past to continue reading. I do encourage you to check out the list and see what kind of useful methods are included in RichIterable.

The following sections will show the methods in RichIterable that have covariant overrides in LazyIterable, MutableCollection, and ImmutableCollection.

LazyIterable

LazyIterable is similar to RichIterable in that it is serial, read-only and inherits all of the same methods. All of the methods that return an Iterable type in RichIterable have covariant overrides in LazyIterable that return LazyIterable. All the non-terminal methods on LazyIterable are lazy. LazyIterable is similar to java.util.stream.Stream in that it supports lazy iteration patterns, but it is different in that a LazyIterable instance can be reused safely for multiple operations without throwing an “exhausted” RuntimeException.

Covariant Overrides

The following methods are overridden from RichIterable on LazyIterable and use covariant returns. What this means is that the return types are more specific in LazyIterable than the return types in RichIterable. The return types must be a subtype of the return type defined in RichIterable.

  1. chunk ➡️ LazyIterable<RichIterable<T>>
  2. collect ➡️ LazyIterable<V>
  3. collectBoolean ➡️ LazyBooleanIterable
  4. collectByte ➡️ LazyByteIterable
  5. collectChar ➡️ LazyCharIterable
  6. collectDouble ➡️ LazyDoubleIterable
  7. collectFloat ➡️ LazyFloatIterable
  8. collectIf ➡️ LazyIterable<V>
  9. collectInt ➡️ LazyIntIterable
  10. collectLong ➡️ LazyLongIterable
  11. collectShort ➡️ LazyShortIterable
  12. collectWith ➡️ LazyIterable<V>
  13. flatCollect ➡️ LazyIterable<V>
  14. flatCollectWith ➡️ LazyIterable<V>
  15. reject ➡️ LazyIterable<T>
  16. rejectWith ➡️ LazyIterable<T>
  17. select ➡️ LazyIterable<T>
  18. selectInstancesOf ➡️ LazyIterable<S>
  19. selectWith ➡️ LazyIterable<T>
  20. tap ➡️ LazyIterable<T>

MutableCollection

MutableCollection inherits all of the methods of RichIterable, and it is mutable as the prefix implies. Methods that mutate the underlying container such as add and remove are provided. All of the methods that return an Iterable type have covariant overrides in MutableCollection that return MutableCollection. All methods on MutableCollection, with the exception of asLazy, are serial and eager. Eager iteration patterns are very easy to understand, as they most closely resemble the code a developer would write by hand implementing an iteration pattern using a for loop.

Covariant Overrides

The following methods are overridden from RichIterable on MutableCollection and use covariant returns. What this means is that the return types are more specific in MutableCollection than the return types in RichIterable. The return types must be a subtype of the return type defined in RichIterable.

  1. aggregateBy ➡️ MutableMap<K, V>
  2. aggregateInPlaceBy ➡️ MutableMap<K, V>
  3. collect ➡️ MutableCollection<V>
  4. collectBoolean ➡️ MutableBooleanCollection
  5. collectByte ➡️ MutableByteCollection
  6. collectChar ➡️ MutableCharCollection
  7. collectDouble ➡️ MutableDoubleCollection
  8. collectFloat ➡️ MutableFloatCollection
  9. collectIf ➡️ MutableCollection<V>
  10. collectInt ➡️ MutableIntCollection
  11. collectLong ➡️ MutableLongCollection
  12. collectShort ➡️ MutableShortCollection
  13. collectWith ➡️ MutableCollection<V>
  14. countBy ➡️ MutableBag<V>
  15. countByEach ➡️ MutableBag<V>
  16. countByWith ➡️ MutableBag<V>
  17. flatCollect ➡️ MutableCollection<V>
  18. flatCollectWith ➡️ MutableCollection<V>
  19. groupBy ➡️ MutableMultimap<V, T>
  20. groupByEach ➡️ MutableMultimap<V, T>
  21. groupByUniqueKey ➡️ MutableMap<V, T>
  22. partition ➡️ PartitionMutableCollection<T>
  23. partitionWith ➡️ PartitionMutableCollection<T>
  24. reject ➡️ MutableCollection<T>
  25. rejectWith ➡️ MutableCollection<T>
  26. select ➡️ MutableCollection<T>
  27. selectInstancesOf ➡️ MutableCollection<S>
  28. selectWith ➡️ MutableCollection<T>
  29. sumByDouble ➡️ MutableObjectDoubleMap<V>
  30. sumByFloat ➡️ MutableObjectDoubleMap<V>
  31. sumByInt ➡️ MutableObjectLongMap<V>
  32. sumByLong ➡️ MutableObjectLongMap<V>
  33. tap ➡️ MutableCollection<T>

ImmutableCollection

ImmutableCollection is similar to RichIterable in that it is read-only and inherits all of the same methods. All of the methods that return an Iterable type have covariant overrides in ImmutableCollection that return ImmutableCollection. All methods on ImmutableCollection, with the exception of asLazy, are serial and eager. Eager iteration patterns are very easy to understand, as they most closely resemble the code a developer would write by hand implementing an iteration pattern using a for loop.

Covariant Overrides

The following methods are overridden from RichIterable on ImmutableCollection and use covariant returns. What this means is that the return types are more specific in ImmutableCollection than the return types in RichIterable. The return types must be a subtype of the return type defined in RichIterable.

  1. aggregateBy ➡️ ImmutableMap<K, V>
  2. aggregateInPlaceBy ➡️ ImmutableMap<K, V>
  3. collect ➡️ ImmutableCollection<V>
  4. collectBoolean ➡️ ImmutableBooleanCollection
  5. collectByte ➡️ ImmutableByteCollection
  6. collectChar ➡️ ImmutableCharCollection
  7. collectDouble ➡️ ImmutableDoubleCollection
  8. collectFloat ➡️ ImmutableFloatCollection
  9. collectIf ➡️ ImmutableCollection<V>
  10. collectInt ➡️ ImmutableIntCollection
  11. collectLong ➡️ ImmutableLongCollection
  12. collectShort ➡️ ImmutableShortCollection
  13. collectWith ➡️ ImmutableCollection<V>
  14. countBy ➡️ ImmutableBag<V>
  15. countByEach ➡️ ImmutableBag<V>
  16. countByWith ➡️ ImmutableBag<V>
  17. flatCollect ➡️ ImmutableCollection<V>
  18. flatCollectWith ➡️ ImmutableCollection<V>
  19. groupBy ➡️ ImmutableMultimap<V, T>
  20. groupByEach ➡️ ImmutableMultimap<V, T>
  21. groupByUniqueKey ➡️ ImmutableMap<V, T>
  22. partition ➡️ PartitionImmutableCollection<T>
  23. partitionWith ➡️ PartitionImmutableCollection<T>
  24. reject ➡️ ImmutableCollection<T>
  25. rejectWith ➡️ ImmutableCollection<T>
  26. select ➡️ ImmutableCollection<T>
  27. selectInstancesOf ➡️ ImmutableCollection<S>
  28. selectWith ➡️ ImmutableCollection<T>
  29. sumByDouble ➡️ ImmutableObjectDoubleMap<V>
  30. sumByFloat ➡️ ImmutableObjectDoubleMap<V>
  31. sumByInt ➡️ ImmutableObjectLongMap<V>
  32. sumByLong ➡️ ImmutableObjectLongMap<V>
  33. tap ➡️ ImmutableCollection<T>

Diamonds grow on trees

Some of the Diamond hierarchies that formed in Eclipse Collections were the result of the Mutable part of the Eclipse Collections hierarchy intersecting with the JDK interfaces (Collection ➡️ MutableCollection, List ➡️ MutableList, Set ➡️ MutableSet). I described some of these diamonds in the following blog.

Other diamonds become visible as you move down the hierarchy to more specific types. There is a pattern in the type hierarchy that formed at the top which results in many instances of tree hierarchies with an Iterable, Mutable, and Immutable type.

For each container type in the following list, there is a tree with an Iterable, Mutable, and Immutable type.

The following diagram shows the diamonds that form as the RichIterable and ListIterable trees connect. A similar diamond formation happens for each container type listed above.

Diamonds growing on trees

A similar symmetry exists in the Eclipse Collections primitive interface hierarchy as well. There are a lot more types in the primitive hierarchy because the hierarchy is a combination of container type and primitive type. At the top of the hierarchy is a type called PrimitiveIterable, which has a small set of common methods between primitive types. The following list shows a sample of the trees that exist for the primitive types in Eclipse Collections using the int type as an example.

The following diagram shows the diamonds that form as the IntIterable and IntList trees connect.

Diamonds grow on primitive trees as well

I hope these diagrams share a small bit of the symmetry that exists in Eclipse Collections. I have included the select method in both the object and primitive hiearchy examples to illustrate the covariant return types at each interface level.

Symmetry in most things

Understanding the symmetry that exists between Iterable, Mutable, and Immutable types across different containers should make it easier to understand the various types, trees and diamonds that exist in the Eclipse Collections library. This library has evolved over almost two decades, and occasionally we still find some asymmetry that exists. We will fix issues like this when there is a measurable benefit. For the most part, if you would expect something to exist based on your understanding of how other things are defined, the symmetry will probably exist and meet your expectations.

Thank you for reading this blog, and I hope you learned something useful about the library along the way!

All 130 Unique Methods on RichIterable

The list below has all 130 unique methods in RichIterable with links to Javadoc. This list is a bit more concise than the Javadoc for RichIterable and should make it easier to find things quickly. The method names appear first with the return types second separated by the ➡️ emoji.

  1. aggregateBy ➡️ MapIterable<K, V>
  2. aggregateInPlaceBy ➡️ MapIterable<K, V>
  3. allSatisfy ➡️ boolean
  4. allSatisfyWith ➡️ boolean
  5. anySatisfy ➡️ boolean
  6. anySatisfyWith ➡️ boolean
  7. appendString ➡️ void
  8. asLazy ➡️ LazyIterable<T>
  9. chunk ➡️ RichIterable<RichIterable<T>>
  10. collect ➡️ RichIterable<V>
  11. collectBoolean ➡️ BooleanIterable
  12. collectByte ➡️ ByteIterable
  13. collectChar ➡️ CharIterable
  14. collectDouble ➡️ DoubleIterable
  15. collectFloat ➡️ FloatIterable
  16. collectIf ➡️ RichIterable<V>
  17. collectInt ➡️ IntIterable
  18. collectLong ➡️ LongIterable
  19. collectShort ➡️ ShortIterable
  20. collectWith ➡️ RichIterable<V>
  21. contains ➡️ boolean
  22. containsAll ➡️ boolean
  23. containsAllArguments ➡️ boolean
  24. containsAllIterable ➡️ boolean
  25. containsAny ➡️ boolean
  26. containsAnyIterable ➡️ boolean
  27. containsBy ➡️ boolean
  28. containsNone ➡️ boolean
  29. containsNoneIterable ➡️ boolean
  30. count ➡️ int
  31. countBy ➡️ Bag<V>
  32. countByEach ➡️ Bag<V>
  33. countByWith ➡️ Bag<V>
  34. countWith ➡️ int
  35. detect ➡️ T
  36. detectIfNone ➡️ T
  37. detectOptional ➡️ Optional<T>
  38. detectWith ➡️ T
  39. detectWithIfNone ➡️ T
  40. detectWithOptional ➡️ Optional<T>
  41. each ➡️ void
  42. flatCollect ➡️ RichIterable<V>
  43. flatCollectBoolean ➡️ R extends BooleanIterable
  44. flatCollectByte ➡️ R extends ByteIterable
  45. flatCollectChar ➡️ R extends CharIterable
  46. flatCollectDouble ➡️ R extends DoubleIterable
  47. flatCollectFloat ➡️ R extends FloatIterable
  48. flatCollectInt ➡️ R extends IntIterable
  49. flatCollectLong ➡️ R extends LongIterable
  50. flatCollectShort ➡️ R extends ShortIterable
  51. flatCollectWith ➡️ RichIterable<V>
  52. forEach ➡️ void
  53. getAny ➡️ T
  54. getFirst ➡️ T
  55. getLast ➡️ T
  56. getOnly ➡️ T
  57. groupBy ➡️ Multimap<V, T>
  58. groupByAndCollect ➡️ R extends MutableMultimap<K, V>
  59. groupByEach ➡️ Multimap<V, T>
  60. groupByUniqueKey ➡️ MapIterable<V, T>
  61. injectInto ➡️ IV
  62. injectIntoDouble ➡️ double
  63. injectIntoFloat ➡️ float
  64. injectIntoInt ➡️ int
  65. injectIntoLong ➡️ long
  66. into ➡️ R
  67. isEmpty ➡️ boolean
  68. makeString ➡️ String
  69. max ➡️ T
  70. maxBy ➡️ T
  71. maxByOptional ➡️ Optional<T>
  72. maxOptional ➡️ Optional<T>
  73. min ➡️ T
  74. minBy ➡️ T
  75. minByOptional ➡️ Optional<T>
  76. minOptional ➡️ Optional<T>
  77. noneSatisfy ➡️ boolean
  78. noneSatisfyWith ➡️ boolean
  79. notEmpty ➡️ boolean
  80. partition ➡️ PartitionIterable<T>
  81. partitionWith ➡️ PartitionIterable<T>
  82. reduce ➡️ Optional<T>
  83. reduceInPlace ➡️ R
  84. reject ➡️ RichIterable<T>
  85. rejectWith ➡️ RichIterable<T>
  86. select ➡️ RichIterable<T>
  87. selectInstancesOf ➡️ RichIterable<S>
  88. selectWith ➡️ RichIterable<T>
  89. size ➡️ int
  90. sumByDouble ➡️ ObjectDoubleMap<V>
  91. sumByFloat ➡️ ObjectDoubleMap<V>
  92. sumByInt ➡️ ObjectLongMap<V>
  93. sumByLong ➡️ ObjectLongMap<V>
  94. summarizeDouble ➡️ DoubleSummaryStatistics
  95. summarizeFloat ➡️ DoubleSummaryStatistics
  96. summarizeInt ➡️ IntSummaryStatistics
  97. summarizeLong ➡️ LongSummaryStatistics
  98. sumOfDouble ➡️ double
  99. sumOfFloat ➡️ double
  100. sumOfInt ➡️ long
  101. sumOfLong ➡️ long
  102. tap ➡️ RichIterable<T>
  103. toArray ➡️ Object[]
  104. toBag ➡️ MutableBag<T>
  105. toBiMap ➡️ MutableBiMap<K, V>
  106. toImmutableBag ➡️ ImmutableBag<T>
  107. toImmutableBiMap ➡️ ImmutableBiMap<K, V>
  108. toImmutableList ➡️ ImmutableList<T>
  109. toImmutableMap ➡️ ImmutableMap<K, V>
  110. toImmutableSet ➡️ ImmutableSet<T>
  111. toImmutableSortedBag ➡️ ImmutableSortedBag<T>
  112. toImmutableSortedBagBy ➡️ ImmutableSortedBag<T>
  113. toImmutableSortedList ➡️ ImmutableList<T>
  114. toImmutableSortedListBy ➡️ ImmutableList<T>
  115. toImmutableSortedSet ➡️ ImmutableSortedSet<T>
  116. toImmutableSortedSetBy ➡️ ImmutableSortedSet<T>
  117. toList ➡️ MutableList<T>
  118. toMap️ ➡️ MutableMap<K, V>
  119. toSet ➡️ MutableSet<T>
  120. toSortedBag ➡️ MutableSortedBag<T>
  121. toSortedBagBy ➡️ MutableSortedBag<T>
  122. toSortedList ➡️ MutableList<T>
  123. toSortedListBy ➡️ MutableList<T>
  124. toSortedMap ➡️ MutableSortedMap<K, V>
  125. toSortedMapBy ➡️ MutableSortedMap<K, V>
  126. toSortedSet ➡️ MutableSortedSet<T>
  127. toSortedSetBy ➡️ MutableSortedSet<T>
  128. toString ➡️ String
  129. zip ➡️ RichIterable<Pair<T, S>>
  130. zipWithIndex ➡️ RichIterable<Pair<T, Integer>>

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.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Donald Raab
Donald Raab

No responses yet

Write a response