Getting Started with Eclipse Collections — Part 2
Learn how to add items to and remove items from collections.
Getting Started with Eclipse Collections
In part 1 of this series, I explained how to download the Eclipse Collections library from Maven Central and create collections using Eclipse Collections. In part 2, I will explain how to add items to and remove items from collections.
Adding to and Removing from Collections
The most basic methods on a collection are the ones that add
items to and remove
items from the collection. Eclipse Collections has flexible and symmetric methods for growing and shrinking both MutableCollection
and ImmutableCollection
instances. Sometimes, there are type specific methods to add items to and remove items from a collection.
Mutable Collections
All MutableCollection
types in Eclipse Collections, except for MutableStack
, are descendants of java.util.Collection
. MutableStack
extends the java.lang.Iterable
interface. The following class diagram shows the interesting mutable interfaces in the JDK and Eclipse Collections that define the adding and removing behavior for various Collection
and Iterable
types.
In the following sections, I will explain and show examples of the adding and removing methods at the various levels of the MutableCollection
type hierarchy as they are introduced. Types in the the MutableCollection
hierarchy extend the type java.util.Collection
. This means the mutable collections in Eclipse Collections are compatible and interchangeable with JDK Collection types. I will start by explaining the methods inherited by the various JDK Collection types including java.util.Collection
, java.util.List
, java.util.Set
.
java.util.Collection
The java.util.Collection
interface in Java has add
and remove
methods that are the primary mechanisms for adding items to and removing items from a collection. The methods take an object as a parameter and both methods return boolean
.
The return result for add
is dependent on the Collection
type. The general contract says the result will be true
if the Collection
is modified as a result of calling the add
method. In the case of a List
, the result will almost always be true
. In the case of a Set
, the result will depend on whether an item was added to the Set
(true
) or already existed in the Set
(false
). I recommend reading the JavaDoc for a particular type to understand the return result for add
.
The return result for remove
is consistent across Collection
types. A result of true
is returned if an items exists in the Collection
and is removed.
The add
method on Collection
takes a generic type as a parameter. The remove
method takes Object
as a parameter. There are questions and answers on StackOverflow why remove takes Object
as a parameter. It does allow the remove
method to be less strict and more flexible.
There are also bulk mutation methods available on java.util.Collection
. The three bulk mutation methods are addAll
, removeAll
, and retainAll
, which all take a Collection
as a parameter. There is also a special method named removeIf
which takes a Predicate
.
The following are examples using the mutating methods from java.util.Collection
on an Eclipse Collections MutableList
.
@Test
public void javaUtilCollection()
{
Collection<String> collection = Lists.mutable.empty();
// add
Assertions.assertTrue(collection.add("1"));
Assertions.assertTrue(collection.add("2"));
Assertions.assertTrue(collection.add("3"));
Assertions.assertEquals(List.of("1", "2", "3"), collection);
// remove
Assertions.assertTrue(collection.remove("2"));
Assertions.assertEquals(List.of("1", "3"), collection);
Assertions.assertFalse(collection.remove("4"));
// addAll
Assertions.assertTrue(collection.addAll(List.of("2")));
Assertions.assertEquals(List.of("1", "3", "2"), collection);
Assertions.assertTrue(collection.addAll(List.of("4", "5")));
Assertions.assertEquals(List.of("1", "3", "2", "4", "5"), collection);
// removeAll
Assertions.assertTrue(collection.removeAll(List.of("2", "4")));
Assertions.assertEquals(List.of("1", "3", "5"), collection);
// retainAll
Assertions.assertTrue(collection.retainAll(List.of("1", "3")));
Assertions.assertEquals(List.of("1", "3"), collection);
// removeIf
Assertions.assertFalse(collection.removeIf(
each -> Integer.parseInt(each) % 2 == 0));
Assertions.assertEquals(List.of("1", "3"), collection);
Assertions.assertTrue(collection.removeIf(
each -> Integer.parseInt(each) % 2 != 0));
Assertions.assertEquals(List.of(), collection);
}
java.util.List
The java.util.List
interface inherits all of the adding and removing methods from java.util.Collection
. List
also includes an add
and addAll
method which allow for inserting elements at a specific index
. There is also a remove
method which takes an index
. The remove
method on java.util.List
is an overload of the remove
method defined on java.util.Collection
. You should take care when calling remove
on a java.util.List
, to make sure you are calling the correct remove
method. There are questions and answers on StackOverflow about the overloaded remove methods on java.util.List
.
The following are examples using the mutating methods from java.util.List
on an Eclipse Collections MutableList
.
@Test
public void javaUtilList()
{
List<String> list = Lists.mutable.with("1", "3", "5");
// add(index, element) - Note: returns void, not boolean
list.add(1, "2");
list.add(3, "4");
var expected1 = Lists.mutable.with("1", "2", "3", "4", "5");
Assertions.assertEquals(expected1, list);
// addAll(index, collection) - Note: returns boolean
Assertions.assertTrue(list.addAll(0, List.of("0")));
var expected2 = Lists.mutable.with("0", "1", "2", "3", "4", "5");
Assertions.assertEquals(expected2, list);
// remove(index) - Note: returns the element removed
Assertions.assertEquals("0", list.remove(0));
Assertions.assertEquals("2", list.remove(1));
Assertions.assertEquals("4", list.remove(2));
var expected3 = Lists.mutable.with("1", "3", "5");
Assertions.assertEquals(expected3, list);
}
java.util.Set
The java.util.Set
interface inherits all of the adding and removing methods from java.util.Collection
. There are no additional adding or removing methods on java.util.Set
. The return result of add
on implementations of java.util.Set
is slightly different than those of java.util.List
. The result of calling add on java.util.List
will always return true
. On an implementation of java.util.Set
, the return result is dependent on whether an equivalent item already existed in the Set
. If an item doesn’t exist, add
will return true
. If an item does exist, add
will return false
.
The following are examples using the mutating methods from java.util.Set
on an Eclipse Collections MutableSet
.
@Test
public void javaUtilSet()
{
Set<String> set = Sets.mutable.empty();
// add - returns true if new element, or false if element exists
Assertions.assertTrue(set.add("1"));
Assertions.assertFalse(set.add("1"));
Assertions.assertEquals(Set.of("1"), set);
}
MutableCollection
The base class for most Mutable collections in Eclipse Collections is MutableCollection
. MutableCollection
extends java.util.Collection
. It adds several methods for growing and shrinking a collection. First, MutableCollection
adds a set of bulk mutating methods that are symmetric with the bulk mutating methods in java.util.Collection
, but that take java.lang.Iterable
instead of java.util.Collection
. The methods are addAllIterable
, removeAllIterable
, retainAllIterable
. There is also a removeIfWith
method which will take a Predicate2
and a parameter of some type.
The following are examples using the mutating methods from MutableCollection
on an Eclipse Collections MutableList
.
@Test
public void mutableCollectionPart1()
{
MutableCollection<String> collection = Lists.mutable.with("1", "2", "3");
// addAllIterable
Assertions.assertTrue(
collection.addAllIterable(Lists.immutable.with("4", "5")));
var expected1 = Lists.mutable.with("1", "2", "3", "4", "5");
Assertions.assertEquals(expected1, collection);
// retainAllIterable
Assertions.assertTrue(
collection.retainAllIterable(Lists.immutable.with("4", "5")));
var expected2 = Lists.mutable.with("4", "5");
Assertions.assertEquals(expected2, collection);
// removeAllIterable
Assertions.assertFalse(
collection.removeAllIterable(Lists.immutable.with("1", "2", "3")));
Assertions.assertEquals(expected2, collection);
Assertions.assertTrue(
collection.removeAllIterable(Lists.immutable.with("5")));
var expected3 = Lists.mutable.with("4");
Assertions.assertEquals(expected3, collection);
// removeIfWith
Assertions.assertFalse(
collection.removeIfWith(Predicates2.equal(), "5"));
Assertions.assertTrue(
collection.removeIfWith(Predicates2.equal(), "4"));
Assertions.assertTrue(collection.isEmpty());
}
The other methods that MutableCollection includes for growing and shrinking a collection are with
, withAll
, without
, and withoutAll
. These methods are symmetric with add
, addAll
, remove
, and removeAll
from java.util.Collection
. The difference is in the return type. The methods from java.util.Collection
all return boolean
. The with*
methods on MutableCollection
all return MutableCollection
.
I wrote about the with*
methods in the following blog.
The with*
methods allow collection instances to become fluent builders for themselves, by allowing chained calls to add
, addAll
, remove
, and removeAll
.
The following are examples using the with*
methods from MutableCollection
on an Eclipse Collections MutableList
.
@Test
public void mutableCollectionPart2()
{
MutableCollection<String> collection = Lists.mutable.with("1", "2", "3");
// with - returns MutableCollection
MutableCollection<String> with =
collection.with("4").with("5");
Assertions.assertSame(with, collection);
var expected1 = Lists.mutable.with("1", "2", "3", "4", "5");
Assertions.assertEquals(expected1, collection);
// withAll - returns MutableCollection
MutableCollection<String> withAll =
collection.withAll(List.of("6"));
Assertions.assertSame(withAll, collection);
var expected2 = Lists.mutable.with("1", "2", "3", "4", "5", "6");
Assertions.assertEquals(expected2, collection);
// without - returns MutableCollection
MutableCollection<String> without =
collection.without("5").without("6");
Assertions.assertSame(without, collection);
var expected3 = Lists.mutable.with("1", "2", "3", "4");
Assertions.assertEquals(expected3, collection);
// withoutAll - returns MutableCollection
MutableCollection<String> withoutAll =
collection.withoutAll(List.of("4"));
Assertions.assertSame(withAll, collection);
var expected4 = Lists.mutable.with("1", "2", "3");
Assertions.assertEquals(expected4, collection);
}
Covariant return types for with* methods
The interfaces MutableList
, MutableSet
, MutableBag
, MutableSortedSet
, MutableSortedBag
, MutableBagIterable
, and MutableSetIterable
all override with
, withAll
, without
, and withoutAll
from MutableCollection
providing a more specific return type. This is an extremely convenient and useful behavior.
The following code examples show how the with
method is covariant by collection type in Eclipse Collections.
@Test
public void covariantReturnTypesForWithMethods()
{
// with - MutableList
MutableList<?> list = Lists.mutable.empty()
.with("1")
.with("2");
Assertions.assertEquals(List.of("1", "2"), list);
// with - MutableSet
MutableSet<?> set = Sets.mutable.empty()
.with("1")
.with("2");
Assertions.assertEquals(Set.of("1", "2"), set);
// with - MutableBag
MutableBag<?> bag = Bags.mutable.empty()
.with("1")
.with("2");
Assertions.assertEquals(Bags.mutable.with("1", "2"), bag);
// with - MutableSortedSet
MutableSortedSet<?> sortedSet = SortedSets.mutable.empty()
.with("1")
.with("2");
Assertions.assertEquals(SortedSets.mutable.with("1", "2"), sortedSet);
// with - MutableSortedBag
MutableSortedBag<?> sortedBag = SortedBags.mutable.empty()
.with("1")
.with("2");
Assertions.assertEquals(SortedBags.mutable.with("1", "2"), sortedBag);
}
The with*
methods are extremely helpful for defining Collector
implementations similar to the ones defined in the Collectors2
class in Eclipse Collections. The covariant overrides that return the specific types make it possible to simply use method references, instead of having to write out a multi-statement lambda.
The following code shows how to use the withAll
method as a method reference on MutableList
and MutableSet
to define the combiner
argument for a Collector
.
@Test
public void collectorsDefinedUsingCovariantWithAllMethods()
{
var mutableListCollector = Collector.of(
Lists.mutable::empty,
MutableList::add,
MutableList::withAll);
MutableList<?> mutableList = List.of(1, 2, 3)
.stream()
.collect(mutableListCollector);
Assertions.assertEquals(List.of(1, 2, 3), mutableList);
var mutableSetCollector = Collector.of(
Sets.mutable::empty,
MutableSet::add,
MutableSet::withAll);
MutableSet<?> mutableSet = Set.of(1, 2, 3)
.stream()
.collect(mutableSetCollector);
Assertions.assertEquals(Set.of(1, 2, 3), mutableSet);
}
Note, it is not nearly as nice or concise defining a Collector
for java.util.ArrayList
or java.util.HashSet
, because addAll
returns boolean
.
The following code shows how a multi-statement lambda is required to implement the combiner
argument of a Collector
for ArrayList
and HashSet
.
@Test
public void collectorsDefinedUsingAddAllMethods()
{
var arrayListCollector = Collector.of(
ArrayList::new,
List::add,
(left, right) -> {
left.addAll(right);
return left;
});
List<?> list = List.of(1, 2, 3)
.stream()
.collect(arrayListCollector);
Assertions.assertEquals(List.of(1, 2, 3), list);
var hashSetCollector = Collector.of(
HashSet::new,
Set::add,
(left, right) -> {
left.addAll(right);
return left;
});
Set<?> set = Set.of(1, 2, 3)
.stream()
.collect(hashSetCollector);
Assertions.assertEquals(Set.of(1, 2, 3), set);
}
MutableBagIterable
The interface MutableBagIterable
is a parent interface of MutableBag
and MutableSortedBag
. In addition to the methods it inherits from java.util.Collection
and MutableCollection
, it also includes addOccurrences
and removeOccurrences
. A Bag
looks like a Collection
and supports the same adding and removing semantics, but internally keeps a Map
of keys to counts. These two methods allow you to increment or decrement the counts directly without having to loop and call add or remove a certain number of times.
The following code example shows how to use addOccurrences
and removeOccurrences
on MutableBagIterable
.
@Test
public void mutableBagIterable()
{
MutableBagIterable<Integer> bagIterable1 = Bags.mutable.with(1);
// addOccurences
bagIterable1.addOccurrences(2, 4);
bagIterable1.addOccurrences(3, 6);
// removeOccurences
bagIterable1.removeOccurrences(2, 2);
bagIterable1.removeOccurrences(3, 3);
Bag<Integer> expected1 = Bags.mutable.withOccurrences(1, 1, 2, 2, 3, 3);
Assertions.assertEquals(expected1, bagIterable1);
}
MutableStack
The one collection interface that does not extend java.util.Collection
or MutableCollection
is MutableStack
. MutableStack
defines push
and pop
for adding and removing elements in LIFO (Last-in, First-out) order. The return result of push
is void
, and the return result of pop
is the element at the top of the stack that is removed. There are variations of pop
that take a count, and that will return a List
of elements popped off the stack.
The following code example shows how to use push
and variations of pop
on a MutableStack
.
@Test
public void mutableStack()
{
MutableStack<Integer> stack = Stacks.mutable.empty();
// push
stack.push(1);
stack.push(2);
stack.push(3);
var expected1 = Stacks.mutable.with(1, 2, 3);
Assertions.assertEquals(expected1, stack);
// pop
Assertions.assertEquals(3, stack.pop());
Assertions.assertEquals(2, stack.pop());
Assertions.assertEquals(1, stack.pop());
// push
expected1.forEach(stack::push);
var expected2 = Stacks.mutable.with(3, 2, 1);
Assertions.assertEquals(expected2, stack);
// pop with count
ListIterable<Integer> popCount = stack.pop(3);
var expected3 = Lists.mutable.with(1, 2, 3);
Assertions.assertEquals(expected3, popCount);
}
Immutable Collections
The ImmutableCollection
interface is the base interface for all immutable collections in Eclipse Collections with the exception of ImmutableStack
. ImmutableCollection
does not extend java.util.Collection
, because if it did, it would essentially be a contractually mutable interface.
In Eclipse Collections, the ImmutableCollection
interfaces are contractually immutable. This means they have no mutating methods defined on the interfaces. The implementations of ImmutableCollection
are also structurally immutable.
The following blog explains how an ImmutableCollection
hierarchy could be defined using Sealed Types in Java to provide the trifecta of immutability — contractual, structural, and verifiable. Eclipse Collections only supports contractual and structural immutability today.
ImmutableCollection
Adding items to or removing items from an ImmutableCollection
is not possible. What is possible is copying the elements of the ImmutableCollection
to a new ImmutableCollection
with a new element or without an existing element. While it is possible to grow and shrink ImmutableCollection
instances this way, it may not be desirable. Every call to newWith*
or newWithout*
will result in a copy of the ImmutableCollection
being made. If a lot of adds or removes are required, it would be advisable to convert the ImmutableCollection
to a MutableCollection
type first and then finally call toImmutable
after all of the adds and/or removes are complete.
Here are some examples of using the methods newWith
, newWithAll
, newWithout
and newWithoutAll
on an ImmutableCollection
.
@Test
public void immutableCollection()
{
ImmutableCollection<Integer> collection = Lists.immutable.empty();
// newWith
ImmutableCollection<Integer> copyAdd1 = collection.newWith(1);
ImmutableCollection<Integer> copyAdd2 = copyAdd1.newWith(2);
ImmutableCollection<Integer> copyAdd3 = copyAdd2.newWith(3);
Assertions.assertEquals(Lists.immutable.with(1), copyAdd1);
Assertions.assertEquals(Lists.immutable.with(1,2), copyAdd2);
Assertions.assertEquals(Lists.immutable.with(1,2,3), copyAdd3);
// newWithout
ImmutableCollection<Integer> copyRemove3 = copyAdd3.newWithout(3);
ImmutableCollection<Integer> copyRemove2 = copyRemove3.newWithout(2);
ImmutableCollection<Integer> copyRemove1 = copyRemove2.newWithout(1);
Assertions.assertEquals(Lists.immutable.with(1,2), copyRemove3);
Assertions.assertEquals(Lists.immutable.with(1), copyRemove2);
Assertions.assertEquals(Lists.immutable.empty(), copyRemove1);
// newWithAll
ImmutableCollection<Integer> copyAddAll =
collection.newWithAll(Lists.mutable.with(1, 2, 3));
Assertions.assertEquals(Lists.mutable.with(1,2,3), copyAddAll);
// newWithoutAll
ImmutableCollection<Integer> copyRemoveAll =
copyAddAll.newWithoutAll(Lists.mutable.with(1, 2));
Assertions.assertEquals(Lists.mutable.with(3), copyRemoveAll);
}
Co-variant return types for newWith* methods
The interfaces ImmutableList
, ImmutableSet
, ImmutableBag
, ImmutableSortedSet
, ImmutableSortedBag
, ImmutableBagIterable
, and ImmutableSetIterable
all override newWith
, newWithAll
, newWithout
, and newWithoutAll
from ImmutableCollection
providing a more specific return type.
The following code examples show how the newWith
method is covariant by collection type in Eclipse Collections.
@Test
public void covariantReturnTypesForNewWithMethods()
{
// newWith - ImmutableList
ImmutableList<?> list = Lists.immutable.empty()
.newWith("1")
.newWith("2");
Assertions.assertEquals(List.of("1", "2"), list);
// newWith - ImmutableSet
ImmutableSet<?> set = Sets.immutable.empty()
.newWith("1")
.newWith("2");
Assertions.assertEquals(Set.of("1", "2"), set);
// with - MutableBag
ImmutableBag<?> bag = Bags.immutable.empty()
.newWith("1")
.newWith("2");
Assertions.assertEquals(Bags.mutable.with("1", "2"), bag);
// with - MutableSortedSet
ImmutableSortedSet<?> sortedSet = SortedSets.immutable.empty()
.newWith("1")
.newWith("2");
Assertions.assertEquals(SortedSets.mutable.with("1", "2"), sortedSet);
// with - MutableSortedBag
ImmutableSortedBag<?> sortedBag = SortedBags.immutable.empty()
.newWith("1")
.newWith("2");
Assertions.assertEquals(SortedBags.mutable.with("1", "2"), sortedBag);
}
Primitive Collections
The methods for adding elements to and removing elements from primitive collections are very similar to their object collection counterparts. On mutable primitive collections, there are methods named add
, addAll
, remove
, removeAll
, with
, withAll
, without
, withoutAll
. There are covariant overrides for the with*
methods. On the immutable primitive collections there are methods names newWith
, newWithAll
, newWithout
, newWithoutAll
and there are also covariant overrides for each of these methods on the various container types (List
, Set
, Bag
, Stack
).
The following are some examples of using various adding/removing methods on mutable primitive collections.
@Test
public void mutablePrimitiveCollections()
{
MutableIntSet set = IntSets.mutable.empty();
// add
Assertions.assertTrue(set.add(1));
Assertions.assertFalse(set.add(1));
// remove
Assertions.assertTrue(set.remove(1));
Assertions.assertFalse(set.remove(1));
// with
set.with(1).with(2).with(3).with(4);
MutableIntSet expected1 = IntSets.mutable.with(1, 2, 3, 4);
Assertions.assertEquals(expected1, set);
// without
set.without(2).without(4);
MutableIntSet expected2 = IntSets.mutable.with(1, 3);
Assertions.assertEquals(expected2, set);
}
The following are some examples of using various growing/shrinking methods on immutable primitive collections.
@Test
public void immutablePrimitiveCollections()
{
// newWith
ImmutableIntSet set1 = IntSets.immutable.empty()
.newWith(1).newWith(2).newWith(3).newWith(4).newWith(5);
Assertions.assertEquals(IntSets.mutable.with(1, 2, 3, 4, 5), set1);
// newWithout
ImmutableIntSet set2 = IntSets.immutable.with(1, 2, 3, 4, 5)
.newWithout(1).newWithout(3).newWithout(5);
Assertions.assertEquals(IntSets.mutable.with(2, 4), set2);
// newWithAll
ImmutableIntSet set3 = set2.newWithAll(IntSets.mutable.with(1, 3, 5));
Assertions.assertEquals(set1, set3);
// newWithoutAll
ImmutableIntSet set4 = set3.newWithoutAll(IntSets.mutable.with(1, 3, 5));
Assertions.assertEquals(set2, set4);
}
Final Thoughts
Thank you for taking the time to read this blog. I hope this will be a good reference for folks to refer to in the future. I tried to be as comprehensive as possible. In the next blog in this series, I will cover in depth how to convert between collection types using converter methods.
Enjoy!
I am the creator of and a Committer for the Eclipse Collections OSS project which is managed at the Eclipse Foundation. Eclipse Collections is open for contributions.