Druckles
Druckles

Reputation: 3782

An implementation of Optional for empty Strings

One of the best things about Optional is it saves all the boilerplate checking for null values in a long chain:

Optional.ofNullable(myService.getSomething())
    .map(secondService::fetch)
    .map(thirdService::fetchAgain)
    // And so forth...

At any point the Optional will jump onto the 'empty' track if map returns a null.

It would be great if something similar could be done for Strings instead of having to check them for String::isEmpty every time:

Optional.ofNullable(entity.getName())
    .filter(String::isEmpty)
    .map(Utils::performSomeOperation)
    .filter(String::isEmpty)
    .or(service::getMostCommonName)
    .filter(String::isEmpty)
    .orElse("Bob");

Something like this:

OptionalString.ofEmptyable(entity.getName())
    .map(Utils::performSomeOperation)
    .or(service::getMostCommonName)
    .orElse("Bob");

The key logic in Optional happens in ofNullable when it calls its check for value == null. Theoretically you could apply any sort of logic in there:

MagicalOptionalString(StringUtils::isNotBlank).ofEmptyable(entity.getName())
    .map(Utils::performSomeOperation)
    .or(service::getMostCommonName)
    .orElse("Bob");

However, Optional is final, preventing any straightforward way of extending this behaviour. So is there an existing, robust implementation of this out there already?

Upvotes: 4

Views: 6918

Answers (2)

Druckles
Druckles

Reputation: 3782

For anyone working in Kotlin, this is really easy to do:

class NonEmptyString private constructor(val Email: String) {
    companion object Factory {
        operator fun invoke(value: String?): T? = value?.let { if (it.isNotEmpty()) NonEmptyString(value) else null }
    }
}

The "static" invoke function conditionally creates a new object depending on whether it's valid or not. And allows you to call it like a constructor (NonEmptyString(value)). The private constructor forces you to use the invoke method.

Because this returns a null if it's not valid, and Kotlin has null-safety built in, it can be really easy to chain. Adding map or flatMap functions is then pretty straight-forward.

See this Code Review question for a more comprehensive, generalisable example I wrote.

Upvotes: 0

Naman
Naman

Reputation: 32028

Trying out a few things to resolve what you were aiming at, and realizing that I would second the thought from VGR as implementing such a use case is a lot of extra work as compared to using the existing methods.

Yet, few details that I could add to after spending some time looking over the implementations -

As a utility, you could implement a static implementation which verifies for both null and isEmpty condition for a string input and returns Optional accordingly. The code could look something like -

private static Optional<String> ofEmptyable(String string) {
    return isNullOrEmpty(string) ? Optional.empty() : Optional.of(string);
}

private static boolean isNullOrEmpty(String target) {
    return target == null || target.isEmpty();
}

this could then replace the usage of the ofNullable which specifically checks for null(the primary purpose of Optional).


Since the expectations in your problem statement were to actually handle the cases per method(map/or/orElse) call as in the optional, one approach similar to OptionalInt could be to implement a custom OptionalString as -

public final class OptionalString {

    private static final OptionalString EMPTY = new OptionalString();

    private final boolean isPresent;
    private final String value;

    private OptionalString() {
        this.isPresent = false;
        this.value = "";
    }

    private static OptionalString empty() {
        return EMPTY;
    }

    private boolean isPresent() {
        return isPresent;
    }

    private OptionalString(String value) {
        this.isPresent = true;
        this.value = value;
    }

    public static OptionalString of(String value) {
        return value == null || value.isEmpty() ? OptionalString.empty() : new OptionalString(value);
    }

    public OptionalString map(Function<? super String, ? extends String> mapper) {
        return !isPresent() ? OptionalString.empty() : OptionalString.of(mapper.apply(this.value));
    }

    public OptionalString or(Supplier<String> supplier) {
        return isPresent() ? this : OptionalString.of(supplier.get());
    }

    String orElse(String other) {
        return isPresent ? value : other;
    }

    public String getAsString() {
        return Optional.of(value).orElseThrow(() -> new NoSuchElementException("No value present"));
    }
}

which could be further implemented for your use case in the following manner -

String customImpl = OptionalString.of(entity.getName())
            .map(OptionalStringTest::trimWhiteSpaces) // OptionalStringTest is my test class name where 'trimWhiteSpaces' operation on String resides
            .or(service::getMostCommonName)
            .orElse("learning");
System.out.println(String.format("custom implementation - %s", customImpl));

where

private static String trimWhiteSpaces(String x) {
    return x.trim();
}

Note - Honestly, I couldn't find the rationale behind not having an OptionalString class upfront in the JDK (the reason why I am stating this is because I suspect there definitely must have been a thought behind it), I believe its just that the radius of my reach is much smaller and I would expect someone credible to add to the details here. IMHO, it seems more like almost all of what you desire is right there using the Optional<String> and which takes us back to the starting of the loop.

Upvotes: 3

Related Questions