Combinators in Isolation #
In Java, optionals are instances of the
generic type
Optional<T>
which has a type parameter T
for the type of wrapped elements.
The combinators map
, filter
and flatMap
discussed with streams
are also available for optionals.
The map
combinator
#
The map
combinator has the following signature1.
<R> Optional<R> map(Function<T,R> function);
Apart from the result type, the type is the same as the type of map
for streams.
The following tests assert that map
applies the given function to the wrapped element
if one is present.
@Test
void testThatMapOnEmptyYieldsEmpty() {
final Optional<String> empty = Optional.empty();
final Optional<Integer> result = empty.map(w -> w.length());
assertTrue(result.isEmpty());
}
@Test
void testThatMapAppliesGivenFunctionToPresentElement() {
final Optional<String> word = Optional.of("Hello");
final Optional<Integer> result = word.map(w -> w.length());
assertEquals(Optional.of(5), result);
}
In the first test the lambda expression is not called. In the second test it is applied to the wrapped string and the result is wrapped in a new optional value.
The static methods Optional.empty
and Optional.of
can be used to create optional values.
Optional.of
throws an exception when null
is passed as argument.
There is also Optional.ofNullable
which returns an empty optional value when passed null
.
The methods isEmpty
and isPresent
can be used to check if a value is present.
The filter
combinator
#
The filter
combinator has the following signature.
Optional<T> filter(Predicate<T> predicate);
Again, the type resembles the type of filter
we have already seen for streams.
The following tests assert that filter
applies the given predicate to a wrapped element.
@Test
void testThatFilterRemovesNonMatchingElement() {
final Optional<String> word = Optional.of("Hello");
final Optional<String> result = word.filter(w -> w.length() > 6);
assertTrue(result.isEmpty());
}
@Test
void testThatFilterKeepsMatchingElement() {
final Optional<String> word = Optional.of("Optionals");
final Optional<String> result = word.filter(w -> w.length() > 6);
assertEquals(word, result);
}
Only matching elements are kept in the result.
The flatMap
combinator
#
The flatMap
combinator has the following signature, resembling flatMap
for streams.
<R> Optional<R> flatMap(Function<T, Optional<R>> function);
The following tests assert that flatMap
applies the given function to the wrapped element
(if one is present) and returns its result.
@Test
void testThatFlatMapOnEmptyYieldsEmpty() {
final Optional<String> empty = Optional.empty();
final Optional<Integer> result =
empty.flatMap(w -> w.chars().boxed().findFirst());
assertTrue(result.isEmpty());
}
@Test
void testThatFlatMapOnPresentReturnsEmptyResultOfGivenFunction() {
final String value = "";
final Optional<String> word = Optional.of(value);
final Optional<Integer> result =
word.flatMap(w -> w.chars().boxed().findFirst());
assertTrue(result.isEmpty());
}
@Test
void testThatFlatMapOnPresentReturnsPresentResultOfGivenFunction() {
final String value = "Hello";
final Optional<String> word = Optional.of(value);
final Optional<Integer> result =
word.flatMap(w -> w.chars().boxed().findFirst());
assertEquals(Character.codePointAt(value, 0), result.get());
}
The terminal operation findFirst
on streams is an example of a method
that may have “no result.”
It returns the first element of a stream represented as optional value
that is empty if the stream is empty.
The method get
on optional values used in the third test
returns the wrapped element if one is present.
If not, the call throws an exception.
The API documentation for get
states that the method orElseThrow
with the same behaviour should be used instead,
but we will stick with get
in this tutorial.
Task: Reasoning #
Think about properties that can be used to manipulate or reason about expressions involving the presented combinators and write more tests to check these properties. Can you express some of the presented combinators using others in a way that would allow arbitrary applications of one combinator to be replaced by a corresponding application of another?
-
Real signatures might be more general than what is shown here because we specialize bounded type parameters. ↩︎