Core Java

First Past the Post

Often, we write code to work out the first answer of a bunch of available ones. Let’s look at that in Java.

01
02
03
04
05
06
07
08
09
10
11
12
public Widget getAppropriateWidget(CustomerRequest request) {
    if (shelfstock.contains(request.getBarcode()) {
        return new ShelfWidget();
    }
    if (backroomStock.contains(request.getBarcode()) {
        return new BackroomWidget();
    }
    if (supplier.contains(request.getEan()) {
        return new SupplierWidget();
    }
    return null;
}

You’ll have to imagine the more complex scenario, hiding behind the above simplified code. What this algorithm does is try options in order of priority until it finds one that works, or it fails, in which case it returns nothing.

Let’s also imagine that the calls to contains are expensive for some reason – perhaps each of these objects is hiding a webservice, or complex database query.

Let’s start out by refactoring the above code two ways. Let’s make it use Optional, and let’s make it use subroutines for each of the methods.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
public Optional<Widget> getAppropriateWidget(CustomerRequest request) {
    Optional<Widget> shelfWidget =
        getShelfWidget(request);
    if (shelfWidget.isPresent()) {
        return shelfWidget;
    }
    Optional<Widget> backroomWidget =
        getBackroomWidget(request);
    if (backroomWidget.isPresent()) {
        return backroomWidget;
    }
    Optional<Widget> supplierWidget =
        getSupplierWidget(request);
    if (supplierWidget.isPresent()) {
        return supplierWidget;
    }
    return Optional.empty;
}
 
// imagine the subsidiary functions

So, this is sort of better than null being the return for not found and is trying hard to use subroutines to make this function describe itself, but it’s having trouble with the fact that each of the Optional objects returned can’t be chained into a chain of responsibility.

We could cheat:

01
02
03
04
05
06
07
08
09
10
11
12
13
Optional<Widget> shelfWidget = getShelfWidget(request);
Optional<Widget> backroomWidget = getBackroomWidget(request);
Optional<Widget> supplierWidget = getSupplierWidget(request);
 
return firstNonEmpty(shelfWidget, backroomWidget, supplierWidget);
 
private static Optional<Widget> firstNonEmpty(
            Optional<Widget> ... options) {
    return Arrays.stream(options)
        .filter(Optional::isPresent)
        .findFirst() // makes an optional of optional here...
        .orElse(Optional.empty());
}

The above code is sort of better but now has to pre-calculate all possible answers before selecting one. We need to be able to avoid costly option calculation if the answer is available sooner.

The First Past the Post with Optionals Solution

Pass either a stream or varargs array to a function, formed of objects that will supply an optional. If any of them supplies a non-empty then it wins.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
// calling code
public Optional<Widget> getAppropriateWidget(CustomerRequest request) {
    return firstAvailable(() -> getShelfWidget(request),
        () -> getBackroomWidget(request),
        () -> getSupplierWidget(request));
}
 
// this is a general purpose solution
// feel free to use it
@SafeVarargs
private static <T> Optional<T> firstAvailable(
        Supplier<Optional<T>> ... options) {
    return Arrays.stream(options)
            .map(Supplier::get)
            .filter(Optional::isPresent)
            .findFirst()
            .orElse(Optional.empty());
}

Published on Java Code Geeks with permission by Ashley Frieze, partner at our JCG program. See the original article here: First Past the Post

Opinions expressed by Java Code Geeks contributors are their own.

Ashley Frieze

Software developer, stand-up comedian, musician, writer, jolly big cheer-monkey, skeptical thinker, Doctor Who fan, lover of fine sounds
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button