Improving Java Optional

Hello there!

Using Java 8 Optional class can be helpful in many situations, but as some of us know, this monad has some kind of limitations. For example, the inexistence of conditional flows when the monadic context is null (Optional class provides only the ifPresent terminal operation that finishes the Optional and returns void). In some situations is possible to solve this limitation with orElse “returns some T value” and orElseGet “returns a Supplier of T”, but as I just said, these methods must return the T or a Supplier<T> “T is the monadic context wrapped object type” that unfortunately are terminal operations “functors can solve this problem, and I will make a post about it ;)”.

So recapitulating the main limitation, we would like to work with more flexible conditional flows and less terminal operations, right? So let’s see some helpful code:

A helpful JDK 8 functional interface is Function<T, R> that takes a param and returns a result, then we can simply use this function to apply some code in conditional flow, because if you have not mapped the Optional monadic context wrapped object type “just to beautify” the Optional always will provides T for its operations, then our functions can receive T and returns some other or the same type.

So to solve the principal Optional limitation, we need to create a function to apply values in else conditions what is something that Optional does not.

import java.util.function.Function;

@FunctionalInterface
public interface ElseFunction<T, R> extends Function<T, R> {

    default R elseApply(final T t) {
        return this.apply(t);
    }
}

The ElseFunction will just wrap a Function with a new method elseApply

The next step is define a Class to wrap an Optional in order to provide more operations and possibilities. So the first step is define the methods to wrap the Optional:

public class Opt<T> {

    private Optional<T> wrapped;

    private Opt(final Optional<T> theOptional) {
        this.wrapped = theOptional;
    }

    public static <T> Opt<T> of(final T value) {
        return new Opt<>(Optional.of(value));
    }

    public static <T> Opt<T> of(final Optional<T> optional) {
        return new Opt<>(optional);
    }

    public static <T> Opt<T> ofNullable(final T value) {
        return new Opt<>(Optional.ofNullable(value));
    }

    public static <T> Opt<T> empty() {
        return new Opt<>(Optional.empty());
    }

The next step is define two possible condition flows, let’s call them as ifPresent and ifNotPresent methods. The ifPresent should recebe some code that returns void and apply this code to T object, to apply that we can use a Consumer<T> and to avoid null values or terminal operations, we can use the Runnable function that just runs some or none code. And let’s put all that in a BiFunction:

private final BiFunction<Consumer<T>, Runnable, Void> ifPresent = (present, notPresent) -> {
     if (this.wrapped.isPresent()) {
        present.accept(this.wrapped.get());
     } else {
        notPresent.run();
     }
     return null;
 };

And of course, we need to reverse the situation in the ifNotPresent flow:

 private final BiFunction<Runnable, Consumer<T>, Void> ifNotPresent = (notPresent, present) -> {
        if (!this.wrapped.isPresent()) {
            notPresent.run();
        } else {
            present.accept(this.wrapped.get());
        }
        return null;
    };

Do you remember the ElseFunction? Yeah we will use that to create ifPresent/ifNotPresent internal flows. To do it, we need to define a curry method in order to currying to the correct flow depending of curry received parameters:

 private static <X, Y, Z> ElseFunction<X, ElseFunction<Y, Z>> curry(final BiFunction<X, Y, Z> function) {
     return (final X x) -> (final Y y) -> function.apply(x, y);
 }

To finish, we just need to create the public Opt Api operations currying its parameters to curry(final BiFunction<X, Y, Z> function) method, and the currying implementation will calls the correct desired flow:

    public ElseFunction<Consumer<T>, ElseFunction<Runnable, Void>> ifPresent() {
        return Opt.curry(this.ifPresent);
    }

    public ElseFunction<Runnable, ElseFunction<Consumer<T>, Void>> ifNotPresent() {
        return Opt.curry(this.ifNotPresent);
    }

You might realize that Opt<T> class just wrap an Optional and do IfPresent and ifNotPresent currying, that’s true, and the main secret is in the ElseFunction that makes it possible to call apply and elseApply methods on any Opt flows, let’s see some examples:

Opt.ofNullable(object)
        .ifPresent()
            .apply(System.out::println)
            .elseApply(() -> System.out.println("Oh no, that's null!"));
Opt.ofNullable(object)
        .ifNotPresent()
            .apply(() -> System.out.println("Oh no, that's still null!"))
            .elseApply(() -> System.out.println("Hello, I'm truly true!!!"));

Is also possible to wrap and use an Optional inside Opt compute sequences when more complex flows are needed:

Opt.ofNullable(Optional.ofNullable(someResult)
        .filter(t -> t.size() > 0)
        .orElse(Collections.emptyList()))
        .ifPresent()
            .apply(r -> {
               // Do something really especial
            })
            .elseApply(() -> {
                // Not so especial but safe :)
            });

These are just some examples, but if you stop to think about, that simple currying to a function makes possible to do practically any conditional situations using funcional programming. Enjoy your safe code! 🙂

You can see the complete code of this example in the following link:

https://github.com/viniciusluisr/improved-optional

 

Advertisements