Apache Commons Collections ListUtils - Introduction
The ListUtils class in the Apache Commons Collections provides utility methods and decorators for List instances. In this post, we will explore all the utility methods in the Apache Commons Collections ListUtils class.
ListUtils defaultIfNull and emptyIfNull
ListUtils#defaultIfNull
The defaultIfNull method takes a list and a defaultList and returns the passed list if it is not null. If the passed list is null, then it returns the defaultList.
Here, we pass two non-null lists. Since the first list (the main list) is not null, it returns it.
List<String> list = List.of("a", "b");
List<String> defaultList = List.of("c");
List<String> result = ListUtils.defaultIfNull(list, defaultList);
System.out.println(result); //[a, b]
If the first list is null, then it returns the second list (the default list) as shown below.
List<String> list = null;
List<String> result = ListUtils.defaultIfNull(list, defaultList);
System.out.println(result); [c]
ListUtils#emptyIfNull
The emptyIfNull takes a list argument and returns it if it is not null. If it is null, then it returns an immutable empty list.
Here, since the passed list is not null, we get it back as the result.
List<String> list = List.of("a", "b");
List<String> result = ListUtils.emptyIfNull(list);
System.out.println(result); //[a, b]
In the below code, since the passed list is null, we get back an immutable empty list.
List<String> list = null;
List<String> result = ListUtils.emptyIfNull(list);
System.out.println(result); //[]
ListUtils#fixedSizeList
The fixedSizeList method takes a list and returns a fixed-sized list, which is backed by the given list. We cannot add or remove elements from the returned list. If we attempt to add or remove elements, we will get an UnsupportedOperationException.
List<String> list = new ArrayList<>(List.of("a"));
List<String> result = ListUtils.fixedSizeList(list);
System.out.println(result); //[a]
// throws java.lang.UnsupportedOperationException: List is fixed size
result.add("b");
But we can change/modify the existing elements using the List#set method, as shown below. Note that for this, the underlying list instance (list here) must support it.
List<String> list = new ArrayList<>(List.of("a"));
List<String> result = ListUtils.fixedSizeList(list);
System.out.println(result); //[a]
result.set(0, "b");
System.out.println(result); //[b]
ListUtils hashCodeForList, isEqualList
ListUtils#hashCodeForList
The hashCodeForList takes a Collection<?> and generates a hash code using the algorithm specified in List#hashCode(). An example is shown below.
List<String> list = List.of("a", "b", "c");
int hashCode = ListUtils.hashCodeForList(list);
System.out.println(hashCode); //126145
ListUtils#isEqualList
We can compare two Collections for equality using the isEqualList method. Internally, it iterates over both the lists using an Iterator and checks if the corresponding pairs of elements in the two lists are equal. In other words, the two lists should be of the same size and should have the same (equal) elements in the same order.
Two examples for equal and unequal lists are given below.
List<String> list1 = List.of("a", "b", "c");
List<String> list2 = List.of("a", "b", "c");
System.out.println(ListUtils.isEqualList(list1, list2)); //true
list1 = List.of("a", "b", "c");
list2 = List.of("a", "b");
System.out.println(ListUtils.isEqualList(list1, list2)); //false
ListUtils#indexOf
The indexOf method takes a list and a Predicate (Apache Commons Collections Predicate) and returns the first index in the given list, which matches the passed predicate.
In the below example, we pass a list having three fruit names. In the first call to indexOf, the predicate checks for a string whose length is 5. The first element in the list matching that predicate is ‘apple’ at index 0. Similarly, the second predicate checks if the length is 6. The string ‘orange’ at index 1 matches the passed predicate.
List<String> list = List.of("apple", "orange", "grapes");
System.out.println(ListUtils.indexOf(list, s -> s.length() == 5)); //0
System.out.println(ListUtils.indexOf(list, s -> s.length() == 6)); //1
If none of the elements matches the predicate, it returns -1.
System.out.println(ListUtils.indexOf(list, s -> s.length() == 3)); //-1
If the passed input list or predicate is null, it returns -1.
System.out.println(ListUtils.indexOf(list, null)); //-1
list = null;
System.out.println(ListUtils.indexOf(list, s -> s.length() == 6)); //-1
ListUtils#lazyList
We can use the lazyList method to populate the elements of the list lazily, i.e., on demand. It lazily computes the element when we call the List#get method and the index passed is greater than the list’s size.
It uses either a Factory or a Transformer to do this. Let us look at both.
Lazy List computation using a Factory
This overload of the lazyList method takes a list and a Factory instance. The Factory (shown below) is a functional interface which takes no argument and returns a value.
@FunctionalInterface
public interface Factory<T> {
/**
* Create a new object.
*
* @return a new object
* @throws FunctorException (runtime) if the factory cannot create an object
*/
T create();
}
In the below example, we start with a list of size one. The Factory is a simple lambda expression returning the same string each time it is invoked. Now, we call the lazyList method with this. The returned list when printed will have only one element.
List<String> list = new ArrayList<>(List.of("a"));
Factory<String> factory = () -> "b";
List<String> lazyList = ListUtils.lazyList(list, factory);
System.out.println(lazyList); //[a]
When we access an element by its index, it will use the passed factory to compute the element at that index. Here, we access the first element which will print the string ‘a’. When we access the second element, it will invoke the passed Factory instance to compute the second element.
System.out.println(lazyList.get(0)); //a
System.out.println(lazyList.get(1)); //b
System.out.println(lazyList); [a, b]
Say when we access an element at index five, the other elements at index 2, 3 and 4 will remain null.
System.out.println(lazyList.get(5)); //b
System.out.println(lazyList); //[a, b, null, null, null, b]
Lazy List computation using a Transformer
In this overloaded lazyList method, we pass an instance of a Transformer to lazily compute the elements. A Transformer is a functional interface which takes an input and returns an output.
Here, the input to the transformer will be an integer which is the index passed to the List#get method.
@FunctionalInterface
public interface Transformer<I, O> {
O transform(I input);
}
Here, we have a Transformer instance which adds (concatenates) the index to the string ‘val:’.
When accessing an element at an index, it uses the passed transformer to compute the element at that index.
List<String> list = new ArrayList<>(List.of("val:0"));
Transformer<Integer, String> transformer = index -> "val:" + index;
List<String> lazyList = ListUtils.lazyList(list, transformer);
System.out.println(lazyList); //[val:0]
System.out.println(lazyList.get(0)); //val:0
System.out.println(lazyList.get(1)); //val:1
System.out.println(lazyList); //[val:0, val:1]
System.out.println(lazyList.get(5)); //val:5
System.out.println(lazyList); //[val:0, val:1, null, null, null, val:5]
ListUtils#longestCommonSubsequence
There are three overloaded longestCommonSubsequence methods.
Longest common subsequence of two strings and two lists
The first overloaded version of longestCommonSubsequence takes two CharSequence instances and returns the longest common subsequence (LCS).
In the below example, since the substring “abc” is the common prefix, it is the LCS as well.
String lcs = ListUtils.longestCommonSubsequence("abc", "abcd");
System.out.println(lcs); //abc
A few other examples are shown below. In the last example, the LCS is an empty string.
System.out.println(ListUtils.longestCommonSubsequence("abcd", "awzcg")); //ac
System.out.println(ListUtils.longestCommonSubsequence("abcd", "cwa")); //a
System.out.println(ListUtils.longestCommonSubsequence("abcd", "cwazd")); //ad
System.out.println(ListUtils.longestCommonSubsequence("abcd", "abcd")); //abcd
System.out.println(ListUtils.longestCommonSubsequence("ab", "cd")); //""
The second overloaded method takes two list instances and returns the LCS of the two lists.
List<String> list1 = List.of("a", "b", "c");
List<String> list2 = List.of("b", "d", "c");
List<String> lcsList = ListUtils.longestCommonSubsequence(list1, list2);
System.out.println(lcsList); //[b, c]
System.out.println(ListUtils.longestCommonSubsequence(List.of("a"), List.of("b"))); //[]
Longest common subsequence with an Equator
The third and final overload takes two lists and an Equator instance which is used to test object equality. The interface Equator has two methods equate and hash as shown below.
public interface Equator<T> {
boolean equate(T o1, T o2);
int hash(T o);
}
This is useful to override the way two objects are checked for equality. Let’s say the lists have binary values as strings. We can write a Equator to convert these to integers before checking for equality.
List<String> list1 = List.of("10", "11", "100");
List<String> list2 = List.of("011", "0100", "111");
Equator<String> equator = new Equator<>() {
@Override
public boolean equate(String o1, String o2) {
return toBinary(o1) == toBinary(o2);
}
@Override
public int hash(String o) {
return Integer.valueOf(toBinary(o)).hashCode();
}
private int toBinary(String b) {
return Integer.parseInt(b, 2);
}
};
lcsList = ListUtils.longestCommonSubsequence(list1, list2, equator);
System.out.println(lcsList); //[11, 100]
In this example, if we had compared the strings as they were, then the leading zeros in some of the strings would have failed the equality check.
ListUtils#predicatedList
It takes a List and a Predicate and returns a decorated list which will be validated by the predicate. In other words, only objects which pass the predicate test can be added to the list. If we attempt to add an object which doesn’t match the predicate, it will throw an IllegalArgumentException.
In this example, the fruits list should have only fruit names whose length is odd. The passed predicate thus checks if the length of the fruit name is odd.
List<String> list = new ArrayList<>(List.of("apple", "mango", "grape"));
List<String> oddLengthFruits = ListUtils.predicatedList(list, s -> s.length() % 2 == 1);
System.out.println(oddLengthFruits); //[apple, mango, grape]
If we add an invalid object (fruit name whose length is even), then we get back an IllegalArgumentException.
//throws java.lang.IllegalArgumentException
oddLengthFruits.add("pear");
We can add valid elements only.
oddLengthFruits.add("pineapple");
System.out.println(oddLengthFruits); //[apple, mango, grape, pineapple]
System.out.println(list); //[apple, mango, grape, pineapple]
Note that we have to make sure the original list is not modified after calling this method. If the original list is modified, it will act as a backdoor for adding invalid objects.
list.add("orange");
//Oops! oddLengthFruits has 'orange'!
System.out.println(oddLengthFruits); //[apple, mango, grape, pineapple, orange]}
ListUtils select, selectRejected
ListUtils#select
The ListUtils#select method takes a Collection and a Predicate and returns only the elements which match the predicate as a list.
With the fruit names list, passing predicate to filter odd and even length strings will return odd and even length fruits, respectively.
If we pass a null predicate, we get back an empty list.
List<String> list = List.of("apple", "orange", "mango", "grape", "pear");
List<String> oddLengthFruits = ListUtils.select(list, s -> s.length() % 2 == 1);
System.out.println(oddLengthFruits); //[apple, mango, grape]
List<String> evenLengthFruits = ListUtils.select(list, s -> s.length() % 2 == 0);
System.out.println(evenLengthFruits); //[orange, pear]
System.out.println(ListUtils.select(list, null)); //[]
ListUtils#selectRejected
The selectRejected method is the inverse of the select method. It selects the elements which don’t match the passed predicate.
List<String> list = List.of("apple", "orange", "mango", "grape", "pear");
List<String> evenLengthFruits = ListUtils.selectRejected(list, s -> s.length() % 2 == 1);
System.out.println(evenLengthFruits); //[orange, pear]
List<String> oddLengthFruits = ListUtils.selectRejected(list, s -> s.length() % 2 == 0);
System.out.println(oddLengthFruits); //[apple, mango, grape]
//null predicate matches no elements
System.out.println(ListUtils.selectRejected(list, null)); //[]
ListUtils#transformedList
The transformedList method takes a list and a Transformer (which we’ve seen before) and returns a new list (decorated by the passed list) that will transform any new entries we add to it. Note that any it will not transform any existing entries in the original list.
We start off with two elements in the list. We create a transformer to append ‘val:’ to a passed string. Then we create a transformed list with these.
List<String> list = new ArrayList<>(List.of("1", "2"));
Transformer<String, String> transformer = s -> "val:" + s;
List<String> transformedList = ListUtils.transformedList(list, transformer);
When we add new elements to the returned list, it will use the transformer before adding it to the underlying original list.
transformedList.add("3");
transformedList.add("4");
System.out.println(transformedList); //[1, 2, val:3, val:4]
We have to make sure the original list is not modified after invoking this method, as it can be a backdoor way to add untransformed objects.
list.add("5");
System.out.println(transformedList); //[1, 2, val:3, val:4, 5]
Intersection and Union
ListUtils#intersection
It takes two lists and returns a new list which has all the elements which are present in both the given lists.
In the below example, the string “c” and “d” are present in both the lists and hence those will be the intersection.
List<String> list1 = List.of("a", "b", "c", "d");
List<String> list2 = List.of("c", "d", "e");
List<String> intersection = ListUtils.intersection(list1, list2);
System.out.println(intersection); //[c, d]
Notice how cardinality of elements works here. Even if an element is present more than once (see below examples) it will appear only once in the result.
System.out.println(ListUtils.intersection(
List.of("a", "b", "c", "d"),
List.of("c", "c", "d", "e"))); //[c, d]
System.out.println(ListUtils.intersection(
List.of("a", "b", "c", "c", "d"),
List.of("c", "d", "e"))); //[c, d]
System.out.println(ListUtils.intersection(
List.of("a", "b", "c", "c", "d"),
List.of("c", "c", "d", "e"))); //[c, d]
When there are no common elements, it returns an empty list.
System.out.println(
ListUtils.intersection(List.of("a", "b", "c", "d"),
List.of("e"))); //[]
ListUtils#union
The union method takes two lists and appends the second list to the first list and returns a new list as a result. It uses List#addAll operation to append the two lists (and hence there can be duplicates in the result).
List<String> list1 = List.of("a", "b", "c", "d");
List<String> list2 = List.of("c", "d", "e");
List<String> union = ListUtils.union(list1, list2);
System.out.println(union); //[a, b, c, d, c, d, e]
RetainAll and RemoveAll
ListUtils#retainAll
The retainAll method takes two lists, a collection and a retain. It returns a new list having all the elements in collection which are also present in retain list.
In this example, since the retain list has strings “a” and “b”, they are the only elements retained from the passed collection.
List<String> collection = List.of("a", "b", "c");
List<String> retain = List.of("a", "b");
List<String> result = ListUtils.retainAll(collection, retain);
System.out.println(result); //[a, b]
If none of the elements in the collection are present in the retain list, we get an empty list back.
System.out.println(ListUtils.retainAll(List.of("a", "b", "c"),
List.of("d", "e"))); //[]
Note that it maintains the cardinality of elements. In other words, if an element appears n times in the passed collection and if that element is present in the retain list, it will appear n times in the result as well.
System.out.println(ListUtils.retainAll(List.of("a", "b", "c", "b"),
List.of("a", "b"))); //[a, b, b]
ListUtils#removeAll
The removeAll method takes two arguments viz., a collection list and a remove list. It removes all the elements in the remove from collection and returns the result as a new list (the passed lists are not modified). In other words, it returns a new list which has all the elements in collection that are not in remove.
Here, since the remove list has strings “a” and “b”, only the string “c” is part of the result list.
List<String> collection = List.of("a", "b", "c");
List<String> remove = List.of("a", "b");
result = ListUtils.removeAll(collection, remove);
System.out.println(result); //[c]
If all the elements in the collection list are present in the remove list, then the result is an empty list.
System.out.println(ListUtils.removeAll(List.of("a", "b", "c"),
List.of("a", "b", "c"))); //[]
With respect to cardinality, if an element present in remove list is present more than once in the collection list, it will remove all of them. Here, there are two “b”’s in the collection list. But since remove list has a “b”, both the “b”’s won’t be part of the result.
System.out.println(ListUtils.removeAll(List.of("a", "b", "c", "b"),
List.of("a", "b"))); //[c]
Subtract and Sum
ListUtils#subtract
It takes two lists and subtracts the elements in the second list from the first list and returns a new list.
List<String> list1 = List.of("a", "b", "c");
List<String> list2 = List.of("b", "c", "d");
List<String> result = ListUtils.subtract(list1, list2);
System.out.println(result); //[a]
The way cardinality is handled is different w.r.t removeAll. If an element is present twice in the first list and only once in the second list, then the result will have that element once.
List<String> list1 = List.of("a", "b", "c", "c");
List<String> list2 = List.of("b", "c", "d");
List<String> result = ListUtils.subtract(list1, list2);
System.out.println(result); //[a, c]
ListUtils#sum
The sum method takes two lists as arguments and returns the sum of them. It is their intersection subtracted from their union. Two examples follow.
List<String> list1 = List.of("a", "b", "c");
List<String> list2 = List.of("b", "c", "d");
List<String> result = ListUtils.sum(list1, list2);
System.out.println(result); //[a, b, c, d]
For the below example the,
- union is - [a, b, c, c, b, c, d]
- intersection is - [b, c]
- subtracting intersection from union gives - [a, c, b, c, d]
List<String> list1 = List.of("a", "b", "c", "c");
List<String> list2 = List.of("b", "c", "d");
List<String> result = ListUtils.sum(list1, list2);
System.out.println(result); //[a, c, b, c, d]
ListUtils#partition
The partition method takes a list and a size as arguments and returns the partitioned list as per the size (the return type is List<List<T>>). Each of the sublists will have the same size, but the last list could be smaller. It produces the sublists by using List#subList method.
In this example, we have a list with 6 elements and we partition it with size as 3. Hence, we will get two lists each of size 3.
List<String> list = new ArrayList<>(List.of("a", "b", "c", "d", "e", "f"));
List<List<String>> partitionedList = ListUtils.partition(list, 3);
System.out.println(partitionedList); //[[a, b, c], [d, e, f]]
If we use size as 4, the last list will have fewer than 4 elements.
List<List<String>> partitionedList = ListUtils.partition(list, 4);
System.out.println(partitionedList); //[[a, b, c, d], [e, f]]
We can add new elements via the returned partitioned list (provided the underlying list allows it). Here we get the first sublist and add a new element (”A”) to it. The affects the partition and the main list as well.
List<String> list = new ArrayList<>(List.of("a", "b", "c", "d", "e", "f"));
List<List<String>> partitionedList = ListUtils.partition(list, 3);
System.out.println(partitionedList); //[[a, b, c], [d, e, f]]
//add new element
partitionedList.get(0).add(0, "A");
System.out.println(partitionedList); //[[A, a, b], [c, d, e], [f]]
System.out.println(list); //[A, a, b, c, d, e, f]
ListUtils#unmodifiableList
It takes a list and returns an unmodifiable list backed by the given list. We cannot mutate via the returned unmodifiable list. Note this includes adding/removing an element and modifying an element at an index.
List<String> list = new ArrayList<>(List.of("a", "b"));
List<String> unmodifiableList = ListUtils.unmodifiableList(list);
System.out.println(unmodifiableList); //[a, b]
//throws java.lang.UnsupportedOperationException
unmodifiableList.add("c");
//throws java.lang.UnsupportedOperationException
unmodifiableList.set(0, "aa");
Conclusion
In this post, we learnt about all the useful utility methods in the Apache Commons Collections ListUtils class.
You can learn more about other utilities from the apache-commons library here.