Utilities for Collections
A Java module providing unmodifiable collection implementations with consistent
behaviour guarantees, fluent builders, and useful utilities. All collections
require non-null values (and keys), and the non-sorted map and set variants
preserve insertion order. See morimekta.net/utils
for procedures on releases.
Getting Started
To add to Maven, include this dependency in pom.xml:
<dependency>
<groupId>net.morimekta.utils</groupId>
<artifactId>collect</artifactId>
<version>5.1.1</version>
</dependency>
To add to Gradle, include this in the dependencies block of build.gradle:
implementation 'net.morimekta.utils:collect:5.1.1'
Unmodifiable Collections
The core of this module is a set of unmodifiable collection classes, largely
inspired by the immutable collections in Guava.
Each collection type provides static factory methods (listOf, setOf, mapOf),
as* conversion methods, stream collectors, and fluent builders.
UnmodifiableList-- An array-backed list that cannot be modified. Supports no-copy slicing throughsubList.UnmodifiableSet-- A hash-table-backed set that cannot be modified. Preserves insertion order when iterating, similar toLinkedHashSet.UnmodifiableMap-- A hash-table-backed map that cannot be modified. Preserves insertion order when iterating, similar toLinkedHashMap.UnmodifiableSortedSet-- An array-backed sorted set implementingNavigableSet. Supports no-copy slicing forsubSet,headSet, andtailSet.UnmodifiableSortedMap-- An array-backed sorted map implementingNavigableMap. Supports no-copy slicing forsubMap,headMap, andtailMap.
All collection builders accept duplicate values. Set builders silently ignore
duplicates (keeping the first entry), and map builders overwrite the value for
duplicate keys, matching the behaviour of standard Set and Map implementations.
Factory Methods and Builders
import net.morimekta.collect.UnmodifiableList;
import net.morimekta.collect.UnmodifiableMap;
import net.morimekta.collect.UnmodifiableSet;
import static net.morimekta.collect.UnmodifiableList.listOf;
import static net.morimekta.collect.UnmodifiableMap.mapOf;
import static net.morimekta.collect.UnmodifiableSet.setOf;
// Factory methods for small collections.
var names = listOf("Alice", "Bob", "Charlie");
var ids = setOf(1, 2, 3);
var ages = mapOf("Alice", 30, "Bob", 25);
// Builders for larger or dynamic collections.
var list = UnmodifiableList.<String>newBuilder()
.add("first")
.addAll(names)
.build();
var map = UnmodifiableMap.<String, Integer>newBuilder()
.put("Alice", 30)
.put("Bob", 25)
.build();
Stream Collectors
Each collection type provides a static toList(), toSet(), toMap(), or
toSortedSet() collector for use with Java streams.
import java.util.List;
import java.util.Set;
import static net.morimekta.collect.UnmodifiableSortedSet.toSortedSet;
interface Example {
String getName();
static Set<String> getNames(List<Example> examples) {
return examples.stream()
.map(Example::getName)
.collect(toSortedSet());
}
}
Common Operations
The UnmodifiableCollection base class provides several convenience methods
that return new unmodifiable collections: append, map, filtered,
sorted, sortedBy, and reversed. Maps provide withEntry and withEntries
for deriving new maps with additional entries.
import static net.morimekta.collect.UnmodifiableList.listOf;
var names = listOf("Charlie", "Alice", "Bob");
// Each operation returns a new unmodifiable collection.
var sorted = names.sorted(); // [Alice, Bob, Charlie]
var filtered = names.filtered(n -> n.length() > 3); // [Charlie, Alice]
var upper = names.map(String::toUpperCase); // [CHARLIE, ALICE, BOB]
var extended = names.append("Diana"); // [Charlie, Alice, Bob, Diana]
Extra Collectors
The ExtraCollectors utility class provides additional stream collectors for
batching and grouping operations.
inBatchesOf(int)-- Splits the stream into a list of lists, each with at most the specified number of items.inNumBatches(int)-- Splits the stream into a fixed number of lists of approximately equal size.groupingBy(Function)-- Groups items by a key function, collecting values into lists. UnlikeCollectors.groupingBy, this accepts duplicate keys gracefully.groupingByAll(Function)-- Groups each item under all keys returned by the key function, so a single item can appear in multiple groups.
import java.util.List;
import static net.morimekta.collect.util.ExtraCollectors.inBatchesOf;
import static net.morimekta.collect.util.ExtraCollectors.groupingBy;
// Process items in batches of at most 10.
List<List<Job>> batches = jobs.stream()
.collect(inBatchesOf(10));
// Group users by their role.
var byRole = users.stream()
.collect(groupingBy(User::getRole));
Utilities
Binary
The Binary class wraps a byte array in an immutable container with a variety
of convenience methods. It supports no-copy slicing, hex and base64 encoding
and decoding, and conversion to and from ByteBuffer, BitSet, and streams.
import net.morimekta.collect.util.Binary;
// Parse from hex, then slice and convert.
var data = Binary.fromHexString("deadbeef");
var slice = data.slice(1, 2);
var hex = slice.toHexString(); // "adbe"
var b64 = data.toBase64();
// Read from an input stream.
var fromStream = Binary.read(inputStream, length);
Pair and Tuple
Pair<F, S> holds two typed values with equals, hashCode, and toString
support. Tuple holds a fixed number of untyped, nullable values backed by an
unmodifiable array.
import net.morimekta.collect.util.Pair;
var pair = Pair.pairOf("key", 42);
String key = pair.first;
int value = pair.second;
LazyCachedSupplier
LazyCachedSupplier provides a lazily initialized, memoized value supplier.
The value is computed on the first call to get() and cached for subsequent
calls. Thread-safe, but may invoke the underlying supplier more than once under
contention. Primitive-typed variants LazyCachedBoolean, LazyCachedInteger,
and LazyCachedLong are also available.
import static net.morimekta.collect.util.LazyCachedSupplier.lazyCache;
var config = lazyCache(() -> loadExpensiveConfig());
// The config is not loaded until the first call to get().
var value = config.get();
SetOperations
The SetOperations utility class provides standard mathematical set operations:
union, intersect, subtract, difference, and cartesianProduct. All
operations preserve the set type: if the input is a SortedSet, the result
will also be a sorted set.
import java.util.Set;
import static net.morimekta.collect.UnmodifiableSet.setOf;
import static net.morimekta.collect.util.SetOperations.*;
var a = setOf(1, 2, 3);
var b = setOf(2, 3, 4);
var u = union(a, b); // {1, 2, 3, 4}
var i = intersect(a, b); // {2, 3}
var s = subtract(a, b); // {1}
var d = difference(a, b); // {1, 4}
SecureHashable
The SecureHashable interface defines a secureHashCode() method that
produces a 64-bit hash with good numerical distribution across the full
long range. Static helper methods are provided for hashing arbitrary objects,
CharSequence values, and byte arrays.