Skip to content

5.0.0 (March 2014)

Compare
Choose a tag to compare
@goldmansachs goldmansachs released this 21 Mar 21:58
· 334 commits to master since this release

Binaries

gs-collections-5.0.0.zip

Javadoc

5.0.0 Javadoc

JDiff

API differences between 4.0.0 and 5.0.0

Acquiring GS Collections

Maven

<dependency>
  <groupId>com.goldmansachs</groupId>
  <artifactId>gs-collections-api</artifactId>
  <version>5.0.0</version>
</dependency>

<dependency>
  <groupId>com.goldmansachs</groupId>
  <artifactId>gs-collections</artifactId>
  <version>5.0.0</version>
</dependency>

<dependency>
  <groupId>com.goldmansachs</groupId>
  <artifactId>gs-collections-testutils</artifactId>
  <version>5.0.0</version>
  <scope>test</scope>
</dependency>

<dependency>
  <groupId>com.goldmansachs</groupId>
  <artifactId>gs-collections-forkjoin</artifactId>
  <version>5.0.0</version>
</dependency>

Ivy

<dependency org="com.goldmansachs" name="gs-collections-api" rev="5.0.0" />
<dependency org="com.goldmansachs" name="gs-collections" rev="5.0.0" />
<dependency org="com.goldmansachs" name="gs-collections-testutils" rev="5.0.0" />
<dependency org="com.goldmansachs" name="gs-collections-forkjoin" rev="5.0.0"/>

New Functionality

Parallel-Lazy Iteration

Previous versions of GS Collections included parallel evaluation and lazy evaluation as separate features. Parallel-eager utility has been available through the ParallelIterate utility class. Serial-lazy evaluation has been available through LazyIterable, the view returned by RichIterable.asLazy(). GS Collections 5.0 adds parallel-lazy evaluation through ParallelIterable, the view returned by asParallel(ExecutorService, int batchSize). The method asParallel is not on interfaces like RichIterable yet, but rather on a few supported collections, including FastList and UnifiedSet.

FastList<Integer> integers = FastList.newListWith(1, 2, 3, 4, 5, 6, 7, 8, 9);

ExecutorService threadPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
int batchSize = 2;
ParallelListIterable<Integer> parallelListIterable = integers.asParallel(threadPool, batchSize);
ParallelListIterable<Integer> evenNumbers = parallelListIterable.select(each -> each % 2 == 0); // deferred evaluation
ParallelListIterable<String> evenStrings = evenNumbers.collect(Object::toString); // deferred evaluation
MutableList<String> strings = evenStrings.toList(); // forced evaluation
threadPool.shutdown();
Assert.assertEquals(FastList.newListWith("2", "4", "6", "8"), strings);

The calls to select and collect are lazy, indicated by the fact that they return subclasses of ParallelIterable. The call to toList() forces evaluation.

The two parameters to asParallel are used to configure parallelism. The code example above sets up a thread pool with one thread per core, which is appropriate for CPU bound tasks. It's possible to configure the thread pool for IO bound tasks, and to share thread pools between multiple calls to asParallel. The batch size determines the number of elements from the backing collection that get processed by each task submitted to the thread pool. The appropriate batch size for CPU-bound tasks will be much larger, usually 10,000 to 100,000. The right batch size should be determined through thorough performance testing.

NOTE: The new parallel-lazy API is experimental and is tagged as @Beta. Until we remove the @Beta annotation, we reserve the right to make incompatible changes to the parallel-lazy API even in minor versions of GS Collections.

API

anySatisfyWith, allSatisfyWith, noneSatisfyWith, countWith, partitionWith, detectWith, and detectWithIfNone on RichIterable

These are the two-argument forms of anySatisfy, allSatisfy, noneSatisfy, count, partition, detect and detectIfNone. They take a Predicate2 instead of a Predicate, and a second argument, which is passed as the second argument to the Predicate2. The two argument forms allow reusing some code blocks that would otherwise have one differing parameter, resulting in less garbage creation. Some of these methods already existed on MutableCollection and were pulled up to RichIterable. Here is a comparison between anySatisfy and anySatisfyWith.

Assert.assertTrue(this.newWith(1, 2, 3).anySatisfyWith(Predicates2.equal(), 2));
Assert.assertTrue(this.newWith(1, 2, 3).anySatisfy(Predicates.equal(2)));

RichIterable.collect<Primitive>(<Primitive>Function, Mutable<Primitive>Collection target)

The new overload RichIterable.collect<Primitive>(<Primitive>Function, Mutable<Primitive>Collection target) is similar to collect<Primitive>(<Primitive>Function), except that the results are gathered into the specified target collection.

ListIterable.toImmutable(), SortedSetIterable.toImmutable(), UnsortedSetIterable.toImmutable(), SortedMapIterable.toImmutable(), UnsortedMapIterable.toImmutable(), StackIterable.toImmutable()

Previously, toImmutable() only existed on MutableCollections. It's now available on the read-only interfaces as well. When called on an immutable collection, it returns the same instance.

MutableStack<Integer> mutableStack = Stacks.mutable.with(1, 2, 3);
Verify.assertInstanceOf(ImmutableStack.class, mutableStack.toImmutable());
Assert.assertNotSame(mutableStack, mutableStack.toImmutable());

StackIterable<Integer> immutableStack = Stacks.immutable.with(1, 2, 3);
Assert.assertSame(immutableStack, immutableStack.toImmutable());

ListIterable.binarySearch(T) and ListIterable.binarySearch(T, Comparator)

Similar to java.util.Collections.binarySearch, but available from the object-oriented API.

LazyIterable.distinct() and LazyIterate.distinct(Iterable)

Similar to toSet(), but returns a LazyIterable (does not force evaluation).

MapIterable.flip()

Returns a new associative array where the position of the keys and values have been flipped. Since the values in the MapIterable are not necessarily unique, flip() returns a Multimap instead of a MapIterable. Since the keys in the MapIterable are unique, flip() returns a SetMultimap instead of the more general Multimap interface. In summary, MapIterable<K, V>.flip() returns SetMultimap<V, K>.

MutableSetMultimap<String, String> expected = UnifiedSetMultimap.newMultimap();
expected.put("odd", "One");
expected.put("even", "Two");
expected.put("odd", "Three");
expected.put("even", "Four");

Assert.assertEquals(expected, UnifiedMap.newWithKeysValues("One", "odd", "Two", "even", "Three", "odd", "Four", "even").flip());

MapIterable.flipUniqueValues()

Similar to MapIterable.flip() but asserts that the values in the MapIterable are unique and thus returns MapIterable instead of Multimap. Throws IllegalArgumentException if the MapIterable contains duplicate values.

MapIterable<Integer, String> map = this.newMapWithKeysValues(1, "1", 2, "2", 3, "3");
MapIterable<String, Integer> flip = map.flipUniqueValues();
Assert.assertEquals(UnifiedMap.newWithKeysValues("1", 1, "2", 2, "3", 3), flip);

MutableMap.getIfAbsentPut(K key, V value)

Gets and returns the value in the map at the specified key. If the map does not contain the key, getIfAbsentPut() puts the value in the map and returns it. Similar to getIfAbsentPut(K key, Function0<? extends V> function), but takes in a value directly instead of a value factory (Function0).

MutableMap<Integer, String> map = UnifiedMap.newWithKeysValues(1, "1", 2, "2", 3, "3");

Assert.assertEquals("4", map.getIfAbsentPut(4, "4")); // mutates
Assert.assertEquals("3", map.getIfAbsentPut(3, "5")); // does not mutate
Verify.assertContainsKeyValue(3, "3", map);
Verify.assertContainsKeyValue(4, "4", map);

MutableMap.add(Pair<K, V>)

Adds the key-value pair to the map. It's a convenience method for working with Pairs, similar to put(K, V).

MutableMap<String, Integer> map = this.newMapWithKeyValue("A", 1);
Assert.assertEquals(Integer.valueOf(1), map.add(Tuples.pair("A", 3)));

Assert.assertNull(map.add(Tuples.pair("B", 2)));
Verify.assertMapsEqual(UnifiedMap.newWithKeysValues("A", 3, "B", 2), map);

MutableBag.setOccurrences(T item, int occurrences)

Mutates the bag to contain the given number of occurrences of the item. Returns true if the bag has been modified as a result of the call to setOccurrences().

MutableBag<String> bag = HashBag.newBag();
MutableBag<String> expected = this.newWith("betamax-tape", "betamax-tape");

Assert.assertTrue(bag.setOccurrences("betamax-tape", 2));
Assert.assertEquals(expected, bag);

ListIterate.reverseForEachWithIndex(List, ObjectIntProcedure)

Iterates over the list in reverse order executing the ObjectIntProcedure for each element. The index passed into the ObjectIntProcedure is the actual index of the range.

Primitive API

Mutable<Primitive>Collection.retainAll

Like Collection.retainAll, but for primitive collections. There are two variants, one that takes a <Primitive>Iterable, and another that takes varargs.

Assert.assertTrue(collection.retainAll(IntArrayList.newListWith(1, 2, 5)));
Assert.assertEquals(this.newMutableCollectionWith(1, 2), collection);
MutableIntCollection collection = this.newMutableCollectionWith(1, 2, 3);
Assert.assertFalse(collection.retainAll(1, 2, 3));
Assert.assertEquals(this.newMutableCollectionWith(1, 2, 3), collection);

keysView() and keyValuesView() on primitive maps

Returns a lazy view of keys or key/value pairs respectively.

keySet() and values() on synchronized, unmodifiable, and immutable primitive maps

These methods already existed on the API but threw UnsupportedOperationExceptions in places. They are fully supported now.

addToValue(key, amount) on mutable primitive maps

Adds the given amount to the value at the given key and returns the updated value. This method exists only for maps where the values are numeric types (not boolean or Object).

MutableByteIntMap map = new ByteIntHashMap();
Assert.assertEquals(1, map.addToValue((byte) 0, 1));
Assert.assertEquals(ByteIntHashMap.newWithKeysValues((byte) 0, 1), map);

Assert.assertEquals(11, map.addToValue((byte) 0, 10));
Assert.assertEquals(ByteIntHashMap.newWithKeysValues((byte) 0, 11), map);

Mutable<Primitive>ObjectMap.putAll(<Primitive>ObjectMap)

putAll() was already implemented on MutableObject<Primitive>Map and <Primitive><Primitive>Map. This rounds out the API.

Reversible<Primitive>Iterable.asReversed()

Returns a reversed view of the primitive iterable. Like ReversibleIterable.asReversed(), but for the primitive API.

Reversible<Primitive>Iterable.toReversed()

Returns a reversed copy of the primitive iterable. Like asReversed() but executes eagerly.

<Primitive>Iterable.injectInto

injectInto was already implemented on primitive collections and has now been pulled up to the <Primitive>Iterable interfaces.

IntIterable arrayList = IntArrayList.newListWith(1, 2, 3);
MutableInteger sum = arrayList.injectInto(new MutableInteger(0), new ObjectIntToObjectFunction<MutableInteger, MutableInteger>()
{
    public MutableInteger valueOf(MutableInteger object, int value)
    {
        return object.add(value);
    }
});
Assert.assertEquals(new MutableInteger(6), sum);

Code Block Factory Methods

Functions.nullSafe(Function)

Returns a null-safe wrapper around the given Function. The wrapper delegates to the valueOf() method of the delegate Function only if the parameter is not null, otherwise it returns null or the provided null replacement value.

MutableList<Integer> squares = FastList.newListWith(1, 2, null, 3).collect(Functions.nullSafe(Functions.squaredInteger()));
Assert.assertEquals(FastList.newListWith(1, 4, null, 9), squares);

MutableList<Integer> squaresWithNullValue =
  FastList.newListWith(1, 2, null, 3).collect(Functions.nullSafe(Functions.squaredInteger(), 0));
Assert.assertEquals(FastList.newListWith(1, 4, 0, 9), squaresWithNullValue);

Functions2.integerAddition()

Creates a Function2 that takes in two Integers as parameters and returns their sum.

HashingStrategies.chain(HashingStrategy...)

Takes a vararg number of hashing strategies and returns a wrapper HashingStrategy that considers objects as equal if all the delegate strategies consider them equal. The hashcode is computed using a strategy similar to code-generated hashCode() methods. It start with the first delegate strategy's computed hashcode, and then repeatedly multiplies the accumulated hashcode by 31 and adds the next computed hashcode for each remaining delegate strategy.

HashingStrategies.identityStrategy()

Returns a HashingStrategy that considers objects as equal only if they are the same object. It uses reference equality in the implementation of equals, and System.identityHashCode in the implementation of computeHashCode.

HashingStrategies.fromFunctions(Function, Function) and HashingStrategies.fromFunctions(Function, Function, Function)

Creates a HashingStrategy from each Function, chains them, and returns the chain.

HashingStrategies.from<Primitive>Function

Similar to HashingStrategies.fromFunction(Function) but optimized for primitives. Implemented in a way that calls to equals and computeHashCode do not create any garbage.

ReversibleIterable and SortedIterable Interfaces

Two new interfaces have been introduced in the GS Collections Hierarchy. ReversibleIterable is an ordered iterable which can be traversed efficiently forwards or backwards. ReversibleIterable has extra API for iterating from the end like asReversed() and reverseForEach(). Lists are the most common ReversibleIterables, and ListIterable extends ReversibleIterable.

ReversibleIterable Inheritance Hierarchy

SortedIterable, is an ordered iterable where the elements are stored in sorted order. Its method comparator() returns the Comparator used to sort the elements, or null if they are sorted in natural order. SortedSetIterable and SortedBag extend SortedIterable.

Changes in the Bag Interface Hierarchy

The inheritance hierarchy of Bags is now more consistent with Sets. We introduced a new interface UnsortedBag, as a sibling of SortedBag. Bag no longer overrides collect, so it returns RichIterable instead of Bag. SortedBag.collect returns ListIterable instead of Bag. UnsortedBag.collect returns UnsortedBag, which means the implementations of UnsortedBag.collect are unchanged. The new interface structure is as follows:

Bag Inheritance Hierarchy

The change to the interface hierarchy changed the serialized forms of TreeBag and UnmodifiableSortedBag.

Changes to serialized forms

Serialized form of synchronized and unmodifiable collections

The hierarchy of synchronized and unmodifiable collections has been changed slightly. AbstractSynchronizedMutableCollection has been extracted from SynchronizedMutableCollection, and all the classes that used to extend SynchronizedMutableCollection like SynchronizedMutableList and SynchronizedMutableSet now extend AbstractSynchronizedMutableCollection directly. Similar changes have been made to the unmodifiable collections.

Serialized form of immutable collections

The serialized forms of ImmutableSet, ImmutableBag and ImmutableMap implementations have been changed.

The ImmutableSet implementations now use a proxy class for serialization.

The implementations of ImmutableMap and ImmutableBag already used proxy classes for serialization. These proxy classes were inner classes inside AbstractImmutableMap and AbstractImmutableBag respectively. These proxy classes were moved to top level, package private classes.