Apache Commons Collections

Apache Commons Collections is an extension to java.util.Collection.

Currently, the Collections package has two commons-collections and commons-collections4. The latest version of commons-collections is 3.2.2, which does not support generics and is no longer officially maintained. The latest version of collections4 is 4.4, and the minimum requirement is Java 8 or above. Compared with collections, it fully supports Java8 features and supports generics. This version is not compatible with older versions, so it was renamed collections4 to avoid conflicts. It is recommended to use this version directly. (Note: The two versions can coexist, please pay attention when using them)

The package structure is as follows:

org.apache.commons.collections4
org.apache.commons.collections4.bag
org.apache.commons.collections4.bidimap
org.apache.commons.collections4.collection
org.apache.commons.collections4.comparators
org.apache.commons.collections4.functors
org.apache.commons.collections4.iterators
org.apache.commons.collections4.keyvalue
org.apache.commons.collections4.list
org.apache.commons.collections4.map
org.apache.commons.collections4.multimap
org.apache.commons.collections4.multiset
org.apache.commons.collections4.properties
org.apache.commons.collections4.queue
org.apache.commons.collections4.sequence
org.apache.commons.collections4.set
org.apache.commons.collections4.splitmap
org.apache.commons.collections4.trie

The maven coordinates are as follows:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-collections4</artifactId>
    <version>4.4</version>
</dependency>

Since it has many functions, here are a few representative ones to briefly introduce their usage.

01. Tools

1. CollectionUtils

String str = null;
List list1 = Arrays.asList(new String[]{"1", "2", "3"});
List list2 = Arrays.asList(new String[]{"1", "2", "4"});
// Determine whether it is empty (null or empty list is true)
CollectionUtils.isEmpty(list1);
//Add element (ignore null elements)
CollectionUtils.addIgnoreNull(list1, str);
// Whether list contains all elements in subList
CollectionUtils.containsAll(list1, list2); // false
// Whether list contains any element in subList
CollectionUtils.containsAny(list1, list2); // true
// list1 minus list2
CollectionUtils.subtract(list1, list2); // ["3"]
// Merge the two lists and remove duplicates
CollectionUtils.union(list1, list2); //["1", "2", "3", "4"]
// Get elements that exist in two lists at the same time
CollectionUtils.intersection(list1, list2); // [1", "2"]

2. ListUtils

List list1 = Arrays.asList(new String[]{"1", "2", "3"});
List list2 = Arrays.asList(new String[]{"1", "2", "4"});
//Same as CollectionUtils, return result is List
ListUtils.subtract(list1, list2); // ["3"]
ListUtils.union(list1, list2); //["1", "2", "3", "4"]
ListUtils.intersection(list1, list2); // [1", "2"]
// Determine whether the contents in the two collections are exactly the same (the order is also consistent)
ListUtils.isEqualList(list1, list2); // false
//If list1 is null, it is converted to an empty List
ListUtils.emptyIfNull(list1);
//Hash all elements in list1
ListUtils.hashCodeForList(list1);

In addition to the two introduced above, there are MapUtils, SetUtils, EnumerationUtils, IterableUtils, etc., which are not very commonly used and will not be introduced in detail.

02. Collection extension

1. FixedSizeList

FixedSizeList is used to decorate another List to prevent its size from being modified. Operations such as adding, deleting, and clearing are not supported. The set method is allowed (because it does not change the list size), see the code example below

List<String> sourceList = new ArrayList<>();
sourceList.add("1");
// Decorate the original list
List<String> list = FixedSizeList.fixedSizeList(sourceList);
list.set(0, "11");
println(list); // [11,2,3]
//The following operations to change the size of the container will throw an exception
list.add("4"); // UnsupportedOperationException("List is fixed size")
list.remove("5"); // UnsupportedOperationException("List is fixed size")
list.clear(); // UnsupportedOperationException("List is fixed size")

2. SetUniqueList

SetUniqueList is used to decorate another List to ensure that there are no duplicate elements. Set is used internally to determine duplication issues.

List<String> sourceList = new ArrayList<>();
sourceList.add("1");
sourceList.add("2");
// list with non-duplicate elements
SetUniqueList<String> list = SetUniqueList.setUniqueList(sourceList);
// If it exists, it will not be processed and will not affect the original order.
list.add("2");
println(list); // [1,2]

3. TransformedList

TransformedList decorates another List to transform the added objects. The add and set methods are affected by this class.

List<String> sourceList = new ArrayList<>();
sourceList.add("1");
sourceList.add("2");
//Convert the list. When adding elements, it will be converted through the second parameter Transformer.
// (The Transformer interface has only one abstract method that can use lambda expressions)
       
// transformingList will not convert existing elements of the original list
TransformedList<String> list = TransformedList.transformingList(sourceList, e -> e.concat("_"));
list.add("a");
println(list); // [1, 2, a_]

// transformedList will transform the existing elements of the original list
list = TransformedList.transformedList(sourceList, e -> e.concat("_"));
list.add("a");
println(list); // [1_, 2_, a_]

4. PredicatedList

PredicatedList decorates another List. When adding elements to the decorated List, the Predicate interface will be called to judge the elements. Only if the elements are matched will they be added to the collection.

List<String> sourceList = new ArrayList<>();
// When adding an element, the second parameter Predicate will be used to determine whether it meets the requirements, and it will be added only if it meets the requirements.
PredicatedList<String> list = PredicatedList.predicatedList(new ArrayList<>(), e -> e.startsWith("_"));
list.add("_4");
println(list); // [_4]

// The following exception will be thrown: java.lang.IllegalArgumentException: Cannot add Object '4'
list.add("4");

5. ListOrderedSet

ListOrderedSet An ordered Set, arranged in the order in which elements are added, similar to List

//Ordered set, sorted according to insertion order
Set<String> set = new ListOrderedSet<>();
set.add("aa");
set.add("11");
set.add("Haha");
println(set); // [aa,11,haha]

6. Bag

The Bag interface is a collection extension with counting function. It inherits the Collection interface and can be used as a collection.

// bag collection with counting function
Bag<String> bag = new HashBag<>();
bag.add("a");
bag.add("b");
bag.add("a");
println(bag.size()); // 3
println(bag.getCount("a")); // 2

In addition to the ones listed above, there are many more collection extensions that I will not introduce here. If you are interested, you can read the source code and study them by yourself.

03. Map extension

1. MultiValuedMap

MultiValuedMap is a little different from a normal Map. The same key allows multiple values to be stored, and these values will be placed in a List. If we use Java’s Map for this function, we need to construct a Map> and add various operations to achieve it.

//List implementation, allowing value duplication
ListValuedMap<String, String> map = new ArrayListValuedHashMap<>();
map.put("user", "Zhang San");
map.put("user", "李思");
map.put("user", "Zhang San");
map.put("age", "12");
// Note: The generic type of value is String, but the get method returns List<String>
List<String> users2 = map.get("user"); // [Zhang San, Li Si, Zhang San]

//Other methods of multiMap
map.containsKey("user"); // true
map.containsValue("Zhang San"); // true
map.containsMapping("user", "Zhang San"); // true

int size = map.size(); // 4

Collection<String> ss = map.values();//[Zhang San, Li Si, Zhang San, 12]
map.remove("user"); // Clear all values of user
//Convert to native map
Map<String, Collection<String>> jMap = map.asMap();

2. CaseInsensitiveMap

Key case insensitive Map

//key is not case sensitive
Map<String, Integer> map = new CaseInsensitiveMap<>();
map.put("one", 1);
map.put("two", 2);
Integer o = map.get("ONE");
println(o); // 1

3. OrderedMap

A sequential Map, sorted in insertion order. If you use hashMap, the keys will be sorted according to the hash value, which may or may not be the same as the insertion order. The number of keys and different JDK versions may affect the order. This is because the hash algorithms of different versions of jdk maps are different, and the hash algorithm is also related to the capacity of the current map.

//key order: according to insertion order
OrderedMap<String, String> map = new ListOrderedMap<>();
map.put("Haha", "1");
map.put("here", "2");
map.put("cc", "3");
map.put("dd", "4");
//The obtained keySet is in order
Set<String> set = map.keySet(); // Haha, here, cc, dd
String nk = map.nextKey("here"); // cc
String pk = map.previousKey("here"); // Haha

4. LRUMap

The LRU (Least recently used) algorithm eliminates data based on the historical access records of the data. Its core idea is that “if the data has been accessed recently, the probability of being accessed in the future is also higher.”

Various caching frameworks support the LRU algorithm, such as EhCache, GuavaCache, Redis, etc. It can be said that it is a very commonly used algorithm.

LRUMap<String, String> map = new LRUMap<>(2);
map.put("aa", "1");
map.put("bb", "2");
map.put("cc", "3");
// The earliest unused aa will be removed
println(map); // [bb:2, cc:3]
// After accessing bb once, if you put it at this time, the earliest cc that has not been accessed will be removed.
map.get("bb");
map.put("dd", "4");
println(map); // [bb:2, dd:4]

5. PassiveExpiringMap

Decorate a Map to remove expired entries when the expiration time is reached. When placing key-value pairs in a Map, this decorator uses an ExpirationPolicy to determine how long the entry should be kept, as defined by the expiration time value. When operating on the Map, it will check whether the element has expired and trigger the deletion operation.

// Survive for one second
int ttlMillis = 1000;
PassiveExpiringMap.ExpirationPolicy<String, String> ep = new PassiveExpiringMap.ConstantTimeToLiveExpirationPolicy<>(ttlMillis);
PassiveExpiringMap<String, String> map = new PassiveExpiringMap<>(ep);
map.put("a", "1");
map.put("b", "2");
map.put("c", "3");
// Wait for one second before retrieving
Thread.sleep(1000);
String vc = map.get("c");
println(vc); // null

6. ReferenceMap

ReferenceMap allows the garbage collector to delete the map. You can specify what type of reference to use to store the map’s keys and values. If you are not using strong references, the garbage collector can delete the map when the keys or values become inaccessible, or the JVM runs out of memory. Using it to make a simple cache will not cause memory overflow due to storing too much content.

There are four main reference types in Java: strong reference, soft reference, weak reference, and virtual reference. We usually use strong references. These four reference types are not the focus of this article. If you don’t understand them, you can search and learn by yourself.

Let’s look directly at the ReferenceMap code example

//All key values use soft references. When the JVM memory is insufficient, the GC will recycle the key-value pairs of soft references.
ReferenceMap<String, String> map = new ReferenceMap<>(AbstractReferenceMap.ReferenceStrength.SOFT, AbstractReferenceMap.ReferenceStrength.SOFT);
// Other map operations... ...

7. BidiMap

BidiMap allows bidirectional lookup between key and value. One of the keys can look up a value, and a value can look up a key just as easily. This interface extends Map. Value is not allowed to be repeated. If repeated, the old key-value pair will be overwritten at the same time.

// Two-way map, key can be obtained through value
// Value is not allowed to be repeated. If repeated, the old value will be overwritten.
BidiMap<String, String> map = new TreeBidiMap<>();
map.put("dog", "dog");
map.put("cat", "cat");
// If the value is repeated, the key will also be overwritten, which is equivalent to "cat2:cat" overwriting "cat:cat".
// map.put("cat2", "cat");
println(map); // {cat=cat, dog=dog}
String key = map.getKey("dog");
println(key); // dog

// Reverse, value becomes key, key becomes value
BidiMap<String, String> iMap = map.inverseBidiMap();
println(iMap); // {dog=dog, cat=cat}
println(iMap.get("dog")); // dog

// The reverse map operation also affects the original map
iMap.put("fish", "fish");
println(iMap); // {dog=dog, cat=cat, fish=fish}
println(map); // {cat=cat, dog=dog, fish=fish}

In addition to the several types of Map extensions just introduced, there are also some extensions that will not be introduced due to space limitations.

05. Summary

Java collections and Maps can be said to be very commonly used data structures. Commons Collections As an extension of Java collections, many collections and Maps with different functions are added, which are ideal for realizing some special needs. Very convenient. If there are corresponding needs, you can consider using it.