Map<String, User> userById;
Map<String, Address> addrById;
Stream<User> users() {
return userById.values().stream();
}
Stream<UserAddress> userAddresses() {
return users()
.map(user -> UserAddress.of(
user, addrById.get(user.id())));
}
you need to be proficient with Java 8
some of what follows is merely my opinion
slides at slides.codefx.org
In APIs |
Exception Handling |
Finding Elements |
Performance |
In APIs |
Exception Handling |
Finding Elements |
Performance |
Using streams in your APIs.
streams are unmodifiable
streams are easy to transform
streams are lazy
⇝ Good way to enrich data in layers.
Map<String, User> userById;
Map<String, Address> addrById;
Stream<User> users() {
return userById.values().stream();
}
Stream<UserAddress> userAddresses() {
return users()
.map(user -> UserAddress.of(
user, addrById.get(user.id())));
}
Map<Address, List<Order>> ordersByAddr;
Stream<Delivery> deliveries() {
return userAddresses().flatMap(this::deliveriesFor);
}
Stream<Delivery> deliveriesFor(UserAdress userAddr) {
return ordersFor(userAddr)
.map(order -> Delivery.of(userAddr, order));
}
Stream<Order> ordersFor(UserAdress userAddr) {
return ordersByAddr
.get(userAddr.address()).stream();
}
streams can only be traversed once
⇝ ideally returned streams can be recreated endlessly
during traversal collection must not be mutated
⇝ stick to layers instead of cycles
(can be harder than it sounds)
decide careful where to call parallel()
Returning streams is ok.
What about passing them as parameters?
streams can only be traversed once!
caller must assume stream is traversed
works for obvious consumer functions
possibly a transformed stream can be returned
// consuming the passed stream
void addUsers(Stream<User> users) {
users.forEach(this.users::add)
}
// transforming the passed stream
Stream<UserAddress> userAddresses(Stream<User> users) {
return users
.map(user -> UserAddress.of(
user, addrById.get(user.id())));
}
returning streams is great
(unmodifiable but transformable)
preferably if streams can be recreated
can be used to gradually enrich data
caller must assume stream is traversed
works for obvious consumers
transformations can be hard to track
never return a traversed stream
(obvious, right?!)
streams can only be traversed once
no mutation during traversal
don’t make chains too long or
debuggability suffers
In APIs |
Exception Handling |
Finding Elements |
Performance |
Handling checked exceptions in Streams.
Stream<User> parse(Stream<String> strings) {
// compile error:
// "incompatible thrown types ParseException"
return strings.map(this::parse);
}
User parse(String userString) throws ParseException {
// ...
}
Which options do we have?
Stream<User> parse(Stream<String> strings) {
return strings
.map(string -> { try {
return parse(string);
} catch (ParseException ex) {
return null;
}})
.filter(Objects::nonNull);
}
super ugly
requires extra clean-up step
handling exception locally can be hard
troublesome elements "disappear"
Stream<User> parse(Stream<String> strings) {
return strings
.map(this::tryParse)
.filter(Objects::nonNull);
}
private User tryParse(String string) {
try { return parse(string); }
catch (ParseException ex) { return null; }
}
somewhat ugly
requires extra clean-up step ("far away")
handling exception locally can be hard
troublesome elements "disappear"
How to "trick the compiler":
static Function<T, R> hideException(
CheckedFunction<T, R, Exception> function) {
return element -> {
try {
return function.apply(element);
} catch (Exception ex) {
return sneakyThrow(ex);
}
};
}
// sneakyThrow does shenanigans with generics
// and unchecked casts to "confuse the compiler"
Stream<User> parse(Stream<String> strings) {
return strings
.map(Util.hideException(this::parse));
}
very surprising (hides a bomb in the stream!)
stream executor has to handle exception
can’t try
-catch(ParseException)
because
checked exceptions need to be declared
exception aborts stream pipeline
Please never do that!
Another Util
method:
static Function<T, R> uncheckException(
CheckedFunction<T, R, Exception> function) {
return element -> {
try {
return function.apply(element);
} catch (Exception ex) {
throw new IllegalArgumentException(
element, ex);
}
};
}
Stream<User> parse(Stream<String> strings) {
return strings
.map(Util.uncheckException(this::parse));
}
stream executor has to handle exception
exception aborts stream pipeline
Another Util
method:
static Function<T, Optional<R>> wrapOptional(
CheckedFunction<T, R, Exception> function) {
return element -> {
try {
return Optional.of(
function.apply(element));
} catch (Exception ex) {
return Optional.empty();
}
};
}
Stream<User> parse(Stream<String> strings) {
return strings
.map(Util.wrapOptional(this::parse))
// Java 9: .flatMap(Optional::stream)
.filter(Optional::isPresent)
.map(Optional::get);
}
requires extra clean-up step
(at least supported by compiler)
troublesome elements "disappear"
Try
Try<T>
is similar to Optional
:
has two states: error or success
allows to process them with functions
parameterized in type of success result
Another Util
method:
static Function<T, Try<R>> wrapTry(
CheckedFunction<T, R, Exception> function) {
return element -> Try.of(
() -> function.apply(element));
}
Try
Stream<Try<User>> parse(Stream<String> strings) {
return strings
.map(Util.wrapTry(this::parse));
}
requires external library (e.g. Javaslang)
encodes possibility of failure in API
makes error available to caller
error is encoded as Exception/Throwable
Either
Either<L, R>
is similar to Optional
:
has two states: left or right
allows to process them with functions
parameterized in types of left and right
if used for failure/success, exception goes left
(by convention)
Either
Another Util
method:
static Function<T, Either<EX, R>> wrapEither(
CheckedFunction<T, R, EX> function) {
return element -> {
try {
return Either.right(
function.apply(element));
} catch (Exception ex) {
return Either.left((EX) ex);
}
};
}
Either
Stream<Either<ParseException, User>> parse(
Stream<String> strings) {
return strings
.map(Util.wrapEither(this::parse));
}
requires external library (e.g. Javaslang)
encodes possibility of failure in API
makes error available to caller
error has correct type
don’t be smart and "trick the compiler"
return a clean stream: no nulls!
ideally, use types to express possibility of failure
Streams don’t cooperate well with checked exceptions.
See that as a chance to use functional concepts
for greater good of code base!
In APIs |
Exception Handling |
Finding Elements |
Performance |
Be careful how you find!
Stream::findFirst
and findAny
:
return an arbitrary element from the Stream
if stream has encounter order,
findFirst
returns first element
Often used after a filter.
Optional<User> findUser(String id) {
return users.stream()
.filter(user -> user.id().equals(id))
.findFirst();
}
Same as the loop:
Optional<User> findUser(String id) {
for (User user : users)
if (user.id().equals(id))
return Optional.of(user);
return Optional.empty();
}
I sometimes see the following:
code’s correctness depends on only
one element passing the filter
but there are no additional checks
⇝ The easy solution might be the wrong one!
(Applies to the loop as well.)
Make sure there is only one element:
Optional<User> findUser(String id) {
return users.stream()
.filter(user -> user.id().equals(id))
.reduce(toOnlyElement());
}
static BinaryOperator toOnlyElement() {
return (element, otherElement) -> {
throw new IllegalArgumentException();
};
}
Instead of reduce
, collect
could be used.
Upsides:
guarantees correctness by failing fast
expresses intent
Downsides:
materializes entire stream
If correctness depends on only one element
surviving an ad-hoc filter:
findFirst
, findAny
do not suffice
use a reducer or collector to assert uniqueness
comes with a performance penalty
In APIs |
Exception Handling |
Finding Elements |
Performance |
Performance is too long to go into.
Great talk by Stuart Marks and Brian Goetz:
Thinking in Parallel (JavaOne 2016)
Usage Patterns |
Value-Based Class |
Not a Monad |
Usage Patterns |
Value-Based Class |
Not a Monad |
The Java community strongly disagrees
on how to best use Optional
.
Some insights into the discussion…
First some basic rules:
never, ever, ever just call get
without checking isPresent
first
prefer functional style
(map
, flatMap
, ifPresent
, orElse
, …)
make everyone setting Optional
to null
buy a round of drinks or wear a silly hat
Nobody (?) wants to see …
Optional.ofNullable(mango)
.ifPresent(System.out::println);
... instead of …
if (mango != null)
System.out.println(mango);
don’t use it unless
absolutely necessary
use it as return value
use it everywhere
API is verbose and invites misuse
makes stack traces harder to debug
not serializable
unsupported by various frameworks
dereferencing reduces performance
instances increase memory consumption
no benefits over explicit null
handling
Optional
sucks
only use it if existing API returns it
unpack quickly!
(Mark Struberg, Stephen Connolly, Hugues Johnson)
was designed as a return value
not serializable
long-lived instances increase
memory consumption
boxing method arguments is verbose
use as return value if
returning null is error-prone
no instance variables
no method parameters
instances should generally be short-lived
(Stuart Marks, Brian Goetz)
⇝ This should be your default choice!
returning null
is always error-prone
rest as before
use as return value whenever
value can be absent
rest as before
(Stephen Colebourne)
using Optional
instead of null
lifts null
-handling into the type system
makes any null
an implementation error
(great for debugging)
performance arguments can be discarded
unless proven to be relevant
avoid optionality through good design
(good recommendation in general)
use Optional
instead of null
everywhere
consider providing overloads
for optional method parameters
(Mario Fusco, me)
String bar(Optional<String> drink) {
return drink.map(this::bar)
.orElseGet(this::bar);
}
String bar(String drink) { /* ... */ }
String bar() { /* ... */ }
Whatever you decide:
pick my recommendation! :)
make it a team decision
put it into your code style
learn over time
Relaxing rules is easier
than making them stricter!
The Mother of All Bikesheds (Stuart Marks)
What’s the Point? (huguesjohnson.com)
Design Of Optional (codefx.org)
Pragmatic Approach (joda.org)
Strict Approach (codefx.org)
Usage Patterns |
Value-Based Class |
Not a Monad |
Optional
implements a new "pattern"
that requires us to be careful with what we do.
Did you RTFM?
Optional
Javadoc says:
This is a value-based class; use of identity-sensitive operations […] on instances of
Optional
may have unpredictable results and should be avoided.
What does it mean?
Future Java (10?) will contain value types:
pass by value
(copied when passed as params)
immutable
no identity
Very similar to today’s primitives.
Class instances have identity:
each new Integer(5)
creates a new instance
they are not identical (!=
, different locks, …)
Value types will have no identity:
there are no two different int 5
only their value matters
From value types to value-based classes:
value types require wrappers/boxes
(just like primitives do today)
value-based classes might turn out
wrapping value types
as an optimization the JVM will
create and destroy them at will
⇝ Wrappers have identity but it is unstable
Map<User, LocalDateTime> userLogins;
Map<LocalDateTime, String> messages;
String lastLoginMessage(User user) {
LocalDateTime lastLogin =
userLogins.get(user); (1)
String message = "Was " + lastLogin;
messages.put(lastLogin, message); (2)
return message;
}
get
might return an instance or a value
put
might receive an instance or a value
final and immutable
equals
, hashCode
, toString
must only rely on instance state
…
no use of ==
, identity hash code,
locking, serialization
(None of this is checked by the JVM.)
java.util
Optional
[Double
, Long
, Int
]
java.time
Duration
, Instant
, Period
,
Year
, YearMonth
, MonthDay
,
Local…
, Offset…
, Zoned…
java.time.chrono
HijrahDate
, JapaneseDate
, MinguaDate
, ThaiBuddhistDate
With Optional
and other value-based classes:
never rely on their identity
mainly no ==
, locking, serialization
If this works out,
performance hit all but disappears!
Value-Based Classes (codefx.org)
Usage Patterns |
Value-Based Class |
Not a Monad |
Optional
saves us from null
at the expense of breaking Monad Laws.
(No math, I promise!)
For a Monad, this should always be true:
Objects.equals(
ofNullable(x).flatMap(f),
f.apply(x));
But:
Function f = s -> of("mango")
Optional ofMap =
ofNullable(null).flatMap(f);
Optional apply = f.apply(null);
// Optional[] != Optional["mango"]
For a Monad, this should always be true:
Objects.equals(
ofNullable(x).map(f).map(g),
ofNullable(x).map(f.andThen(g)));
But:
Function f = s -> null;
Function g = s -> "mango";
Optional map = of("kiwi").map(f).map(g);
Optional then = of("kiwi").map(f.andThen(g));
// Optional[] != Optional["mango"]
Optional
maps null
to empty()
flatMap
and map
are not executed
on empty optionals
the first occurrence of null
/empty
stops the chain of executions
refactoring can change
which code gets executed
functions that can "recover" from null
might not get executed
particularly error-prone when
functions have side effects
(they generally should not but it happens)
be aware that Optional
is no well-behaved monad
see it as a way to avoid handling null
be aware that refactoring can cause problems
if null
was special cased
More on Optional Being Broken (atlassian.com)
… and Why It Matters (sitepoint.com)
interface is transitional (old and new outline)
default methods ensure existing code works
client moves from old to new outline
default methods ensure code keeps working
removes old outline
Reasonable default impl exists:
add the method with default impl
internal impls can override
internal callers use new method
external callers use the method
That’s it.
No reasonable default impl exists:
add method with default impl throwing UOE
override method in all internal impls
external impls override the method
external callers use the method
make method abstract
internal callers use new method
No external impls exist:
deprecate method
internal callers stop calling method
external callers stop using the method
remove the method
(No default methods required.)
External impls exist:
deprecate method
provide default impl throwing UOE
internal callers stop calling method
external callers stop using the method
external impls of the method are removed
remove the method
Applies with new signature (name, parameters, …),
where methods are "functionally equivalent".
Otherwise it’s a matter of adding new
and removing old method.
add new with default impl calling old
provide default impl of old calling new
deprecate old
internal impls override new instead of old
internal callers use new instead of old
Wtf, circular call?
ensures it does not matter which version is impl’d
must be thoroughly documented; tests help
external impls override new instead of old
external callers use new instead of old
make new abstract
remove old
If clients can be expected to update their code
default methods allow interface evolution
without breaking client code.
Mode is always the same:
release version with transitional outline
give clients time to update
release version with new outline
Interface Evolution (codefx.org)
New Javadoc Tags (codefx.org)
Nicolai Parlog
codefx.org
sitepoint.com/java
@nipafx /
+NicolaiParlog
Module System: tiny.cc/j9ms
Newsletter: tiny.cc/fx-weekly
two-day Java 9 course
Zürich, 20.-21.04.
⇝ register at 42talents.com
or hire me!