Aug 14, 2019

Eclipse Collections - the features your collections need

Deny it or not data structures are important. Picking the right one will enormously increase the performance of your program/product/application.

Many (mainstream) programming languages come with a collection library. That provides APIs and implementations to make it easy for end-users.

These implementations are fast and built to make it easier for end-users.

Providing too many options will increase the learning curve of the language, providing too less will lead to cumbersome implementation by the users. So languages have to be very careful in what they provide.

Eclipse collections provide optimized and efficient implementations of collections in Java.

Eclipse collections also added a few additional data structures that are not natively available in the core Java.

But the most important thing is that Eclipse-Collections provides elegant, functional, and fluent APIs that you can work with.

The top reasons why I love eclipse collections are:

  • APIs are awesome. They are:
    • Functional
    • Lazy
    • Parallel
    • Eager (while optimized)
  • provides both immutable and mutable collections
  • provide highly optimized and memory-efficient implementation

Set things up

If you are using a Maven project include the eclipse collection dependencies like below:

<dependency
    <groupId>org.eclipse.collections</groupId>
    <artifactId>eclipse-collections</artifactId>
    <version>10.0.0</version>
</dependency>

If you are using Gradle then import it:

compile 'org.eclipse.collections:eclipse-collections-api:10.0.0'
compile 'org.eclipse.collections:eclipse-collections:10.0.0'

Ingredients

The Eclipse Collections consists of the following data structures:

  1. List
  2. Set
  3. Map
  4. BiMap
  5. MultiMap
  6. Bag
  7. Stack
  8. Pair and others

All those data structures include the following implementations:

  1. Mutable
  2. Immutable
  3. Lazy
  4. Parallel
  5. Ordered
  6. Sorted
  7. Fixed
  8. Primitive and others

Note not all the implementations for all the collections.

Code, Code, Code...

There are few excellent code katas available. They are here.

Let us start with a List.

To instantiate a new mutable list we can do the following:

MutableList<Integer> firstTenNumbers = Lists.mutable.with(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);

You can instantiate the list with of.

MutableList<Integer> firstTenNumbers = Lists.mutable.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);

Reterival

We can retrieve the elements using classic get(index) way. The Eclipse Collections also provide a getFirst() and getLast() method to retrieve the first and the last elements respectively.

firstTenNumbers.get(4);       // 4
firstTenNumbers.getFirst();  // 0
firstTenNumbers.getLast();  // 9

In functional paradigm, take | takeWhile | drop | dropWhile APIs are awesome because they help to take and drop certain elements from the list without mutating them. With Eclipse Collections we can do that in Java.

drop and take

The functions drop and take each take a non-negative integer n. The drop returns all the elements after the given index while the take gives back all the elements until the index.

firstTenNumbers.take(3); // 0, 1, 2
firstTenNumbers.drop(3); // 3, 4, 5, 6, 7, 8, 9

The functions drop and take can be specified by the following function:

list.take(n) + list.drop(n) == list

dropWhile and takeWhile

They are technically drop and take but instead of taking a number as an argument, they take a predicate function.

The takeWhile function will return all the values until the predicate returns true.

firstTenNumbers.takeWhile(i -> i % 2 == 0); // 0

The dropWhile function will return all the values in the list after which the predicate returns true.

firstTenNumbers.dropWhile(i -> i % 2 == 0); // 1, 2, 3, 4, 5, 6, 7, 8, 9

convertors

Often times, we will need converters. The role of converters is to change the list from one form to another. That is from Mutable to Immutable. We can achieve that via toImmutable function.

MutableList<T> iCanChange = Lists.mutable.with(T... args);
ImmutableList<T> iCannotChange = iCanChange.toImmutable(); // From now onwards I cannot Change

We will need to reverse our list and the API provides us with toReversed function to achieve the same.

MutableList<T> normalList = Lists.mutable.with(T... args);
normalList.toReversed();

The stack data structure is inbuilt in the library. We can convert the list into stack using toStack.

MutableList<Integer> firstTenNumbers = Lists.mutable.with(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
firstTenNumbers.toStack().pop(4); // 9, 8, 7, 6

There are many other converters available like toSet, toSortedSet, toMap, toBiMap, and others

zip

The function zip will take a pair of lists and convert them into list of pairs. That is:

[T], [U] =>  [T, U]
MutableList<Integer> houseNumbers = Lists.mutable.with(123, 456, 789);
MutableList<String> owners = Lists.mutable.with("Ram", "Raj", "Rahul");

owners.zip(houseNumbers); // (Ram:123), (Raj:456), (Rahul:789)

It is also important to note that the length of the lists need not be equal.

The function zip has many uses like getting the Scalar Product.

Select & Reject

Select and Reject are nothing but filters. Both of them will accept a predicate. The Select select only those are true. The Reject select only those are are returning false.

MutableList<Integer> evenNumbers = Lists.mutable.with(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
                                        .select(i ->  i % 2 == 0);
// 0, 2, 4, 6, 8
MutableList<Integer> oddNumbers = Lists.mutable.with(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
                                        .reject(i ->  i % 2 == 0);
// 1, 3, 5, 7, 9

Note you can also do rejectWith and selectWith.

partition

A PartitionMutableCollection is the result of splitting a mutable collection into two mutable collections based on a Predicate. - Eclipse Collections

MutableList<Integer> firstTenNumbers = Lists.mutable.with(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
System.out.println(firstTenNumbers.partition(i -> i % 2 == 0).getSelected()); // 0, 2, 4, 6, 8

The partition will accept the predicate and will split the list based on the predicate.

Note you can also do partitionWith.

groupBy

Sometimes we will need to group the elements together, we can use groupBy for this. The groupBy will accept the predicate function and groups the element based on the predicate's return value.

MutableList<Integer> firstTenNumbers = Lists.mutable.with(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
System.out.println(firstTenNumbers.groupBy(i -> i % 2 == 0 ? "even" : "odd"));
// {even=[0, 2, 4, 6, 8], odd=[1, 3, 5, 7, 9]}

collect

The collect is more or less analogous to map. It takes a predicate function. Then applies the function on all the values of the list and returns a new list with the updated values.

MutableList<Integer> firstTenNumbers = Lists.mutable.with(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
System.out.println(firstTenNumbers.collect(i -> i + 1));
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

We can even do flatCollect.

distinct

Distinct as the name implies collect the distinct values in the array.

MutableList<Integer> distinctValueList = Lists.mutable.with(1, 1, 2, 3, 4).distinct();
// [1, 2, 3, 4]

anySatisfy, allSatisfy, and noneSatisfy

The any|all|noneSatisfy makes it easy to check and also it is evaluated lazily. For example

MutableList<Character> gradeList = Lists.mutable.with('A', 'A', 'F', 'B');
Boolean isPass = gradeList.allSatisfy(c -> c != 'F'); // False
Boolean isFail = gradeList.anySatisfy(c -> c == 'F'); // True
Boolean isPass = gradeList.noneSatisfy(c -> c == 'F'); // False

max, min and sumOfInt

As the name implies they get the maximum, minimum and sumOfInt of the values in the list provided.

MutableList<Integer> firstTenNumbers = Lists.mutable.with(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
System.out.println(firstTenNumbers.min()); // 0
System.out.println(firstTenNumbers.max()); // 9
System.out.println(firstTenNumbers.sumOfInt(i -> i)); // 45

There is a lot of other APIs available, check out the full list here.

We will continue with even more in-depth tutorial about Eclipse Collections.


Up Next