-
Notifications
You must be signed in to change notification settings - Fork 4
Streams and Lambdas
Note: Content of this page applies only to versions 2.0.0 and above.
Java 8 added Streams to the standard library and lambda expressions to the language. Starting with version 2.x these are now directly supported by Javaimmutable collections. Streams allow you to operate on all elements of a collection either serially or in parallel. Javimmutable supports Streams through a new interface named IterableStreamable
. This interface defines two methods for creating Streams:
Stream stream();
Stream parallelStream();
The stream()
method returns a Stream for processing the collection in a single thread. The parallelStream()
method returns a Stream for processing the collection using multiple threads. (Using parallelStream()
is similar to using stream().parallel()
).
All collections implement the IterableStreamable
interface. In addition several collections have view methods that return IterableStreamable
objects for operating on a slice of the collection. For example, JImmutableMap
defines these view methods:
-
keys()
returns a view for operating on just the keys in the map -
values()
returns a view for operating on just the values in the map
Other collections define these view methods as well. JImmutableMultiset
defines its own view methods:
-
entries()
returns a view for operating onJImmutableMap.Entry
objects containing each value plus its count -
ocurrences()
returns a view for operating on all values in the multiset with each value appearing its count times in the stream
While streams are extremely powerful they do imply some overhead that you may not need all of the time. For cases where you simply want to perform some operations on a collection within a single thread you can use these additional utility methods defined by IterableStreamable
. Most of them operate on all of the values in the collection by passing each value to a lambda expression to yield a result.
The collect()
methods can be used to copy values from the collection into another. All of them accept a destination collection to receive the values. Two of the variations also accept a Predicate
used to determine which values should be copied.
JImmutableList<String> source = JImmutables.list("axle", "wheel", "apple", "wall");
JImmutableSet<String> copied = source.collect(JImmutables.set());
// copied now contains "apple", "axle", "wall", and "wheel"
copied = source.collect(2, JImmutables.set());
// copied now contains "axle" and "wheel"
copied = source.collect(JImmutables.set(), str -> str.startsWith("a"));
// copied now contains "axle" and "apple"
copied = source.collect(1, JImmutables.set(), str -> str.startsWith("w"));
// copied now contains "wheel"
The transform()
methods are similar to collect()
but they use a lambda to transform each element of the collection before adding the result to the collection. The transformSome()
methods are similar but the lambdas return Holder
objects instead of values. Empty Holder
s are ignored. Non-empty Holder
s have their value copied to the collection.
JImmutableSet<String> source = JImmutables.set("axle", "wheel", "apple", "wall");
JImmutableList<String> transformed = source.collect(JImmutables.list(), str -> str.toUpper());
// transformed now contains "APPLE", "AXLE", "WALL", and "WHEEL"
transformed = source.transform(2, JImmutables.list(), str -> str.substring(1));
// transformed now contains "xle" and "heel"
transformed = source.transformSome(JImmutables.list(), str -> str.length() == 4 ? Holders.of() : Holders.of(str));
// transformed now contains "wheel" and "apple"
transformed = source.transformSome(1, JImmutables.list(), str -> str.length() == 4 ? Holders.of() : Holders.of(str));
// transformed now contains "wheel"
The partition()
method uses a predicate to split a copy collection's values to two other collections. One receives the values for which the predicate returned true. The other receives those for which the predicate returns false. The resulting collections are packaged into a Partitions
object and returned to the caller.
JImmutableSet<String> source = JImmutables.set("axle", "wheel", "apple", "wall");
Partitions parts = source.partition(JImmutables.set(), JImmutables.set(), str -> str.startsWith("a"));
// parts.getMatched() contains "axle" and "apple"
// parts.getUnmatched() contains "wheel" and "wall"
Sometimes you need to process all elements to produce a single result. For example you may want to compute the sum of a list of integers. The IterableStreamable
interface provides two methods for doing so. They have different names because their return types are different but they both perform essentially the same operations.
The inject()
method accepts a starting value and a lambda that accepts the current value plus an element from the collection. The lambda is invoked once for each element. The first call to the lambda receives the initial value and the first element. All other calls receive the result of the previous call and an element. The result of the inject()
call is the final value returned by the lambda.
JImmutableList<Integer> values = JImmutables.list(1, 2, 3, 4, 5, 6, 7, 8);
assertEquals(36, values.inject(0, (s,x) -> s + x); // 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8
assertEquals(-18, values.inject(18, (s,x) -> s - x); // 18 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8
assertEquals(16, values.inject(0, (s,x) -> s + (x / 2)); // 0 + 0 + 1 + 1 + 2 + 2 + 3 + 3 + 4
The reduce()
method is similar to inject()
except that no initial value is provided when calling the method. Instead the first element is used as the initial value. Because it's possible for the collection to be empty the reduce()
method returns a Holder
which will be empty if the collection was empty. Otherwise the Holder
contains the final value.
JImmutableList<Integer> empty = JImmutables.list();
JImmutableList<Integer> values = JImmutables.list(1, 2, 3, 4, 5, 6, 7, 8);
assertEquals(Holders.of(), empty.reduce((s,x) -> s + x); // no values so Holder is empty
assertEquals(Holders.of(36), values.reduce((s,x) -> s + x); // 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8
assertEquals(Holders.of(-34), values.reduce((s,x) -> s - x); // 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8
assertEquals(Holders.of(16), values.reduce((s,x) -> s + (x / 2)); // 0 + 1 + 1 + 2 + 2 + 3 + 3 + 4
The count()
method returns the number of elements or the number of elements matching a Predicate
. The first()
method returns the first element matching a Predicate
. The allMatch()
and anyMatch()
methods test the elements and return the expected boolean result.
JImmutableSet<String> source = JImmutables.insertOrderSet("axle", "wheel", "apple", "wall");
assertEquals(4, source.count());
assertEquals(3, source.count(str -> str.contains("a")));
assertEquals("axle", source.first(str -> str.startsWith("a")));
assertEquals(true, source.anyMatch(str -> str.startsWith("a")));
assertEquals(false, source.allMatch(str -> str.startsWith("a")));