MathJax

Monday, February 29, 2016

Write This, Not That!: Java 8 Optionals


The point of java.util.Optional is to free us from compulsive null-checking throughout our code base by using the type checker to force us to recognize that a value may not be present. Optionals make great return types for query methods. Transitioning from an imperative/null world to a functional/Optional world can feel strange in a vacuum. Here are some common themes taken from reviewing code over the last year.

Write this:
final MyData defaultData = ... ;
return optionalReturningMethod().orElse(defaultData);
Not that:
final MyData defaultData = ... ;
final Optional<MyData> oData = optionalReturningMethod();
if (oData.isPresent()) {
    return oData.get();
} else {
    return defaultData;
}
Write this:
final MyVariable var = ... ;
final MyData nullableData = externalMethodThatMayReturnNull(var);
return Optional.ofNullable(nullableData);
Not that:
final MyVariable var = ... ;
final MyData nullableData = externalMethodThatMayReturnNull(var);

if (nullableData == null) {
    return Optional.empty();
} else {
    return Optional.of(nullableData);
}
Write this:
public Optional<MyData> myMethod() {
    final MyVariable var = ... ;
    final MyData nullableData = externalMethodThatMayReturnNull(var);
    return Optional.map(Utilities::myTransformation);
}
Not that:
public MyData myMethod() {
    final MyVariable var = ... ;
    final MyData nullableData = externalMethodThatMayReturnNull(var);

    if (nullableData == null) {
        return null;
    } else {
        return Utilities.myTransformation(nullableData);
    }
}
Write this:
optionalReturningMethod().ifPresent(Actions::useMyData);
Not that:
final Optional<MyData> optionalData = optionalReturningMethod();
if (optionalData.isPresent()) {
    Actions.useMyData(optionalData.get());
}
Write this:
return yourOptional().map(External::nullableFromValue);
Not that:
return yourOptional()
        .map(External::nullableFromValue)
        .flatMap(Optional::ofNullable);

A surprise: .map() will filter the original optional to Optional.empty() rather than wrap a null value with an Optional. Nerds, note that this means java.util.Optional is not actually a monad (like how the "functions" of software developers aren't the functions of mathematicians). We'll see how this goes. We're denied some of the beauty of the math of category theory (which is studied in spite of having the most boring name in existence) in exchange for not needing to tack on a .flatMap(Optional::ofNullable) ourselves.

Write this:
public Optional<TheirSubSubData> subSubData(TheirData data) {
    return Optional.ofNullable(data)
            .map(TheirData::getSubData)
            .map(TheirSubData::getSubSubData);}
Not that:
public TheirSubSubData subSubData(TheirData data) {
    if (data != null) {
        final TheirSubData subData = data.getSubData();
        if (subData != null) {
            return subData.getSubSubData();
        }
    }
}

return null;
Note that if Optional were a full-blown monad, we would need to have a .flatMap(Optional::ofNullable) after each of these .map() calls.

Write this:
public class MyClass {
    private final FieldType field;

    public MyClass(FieldType constructorArg) {
        if (constructorArg == null) {
            throw new NullPointerException();
        }
        this.field = constructorArg;
    }
}

// or

@lombok.RequiredArgsConstructor
public class MyClass {
    @lombok.NonNull private final FieldType field;
}
Not that:
public class MyClass {
    private final Optional<FieldType> field;
    public MyClass(FieldType constructorArg) {
        this.field = Optional.ofNullable(constructorArg);
    }
}
Wanting to use Optional fields for a class is a strong signal that you actually have a subclass.

Finally, if you find yourself using a lot of Optional::flatMap, just be cautious. It's easy to use .flatMap() as a crutch for not properly breaking down your code. If the reason you're using .flatMap() instead of .map() isn't 100% obvious, call over someone else to see if your code makes sense or you're just too deep inside your own head.

No comments:

Post a Comment