lpkej
lpkej

Reputation: 485

Java 8 reduce method calls depending on a value

How to optimize method that returns the first non empty value returned of the checkN() methods and prevent the rest method calls and isEmpty() calls:

private String validate() {
    String message = check1();

    if (message.isEmpty()) {
        message = check2();
    }

    if (message.isEmpty()) {
        message = check3();
    }

    // total 5+ checks been called

    return message;
}

#1 I thought of using stream filter and return first that has value, but it requires to call each check:

return Stream.of(check1(), check2(), check3(), ...).filter(message -> !message.isEmpty()).findFirst().orElse("");

SOLVED

as M A suggested, finalized solution for me was:

public static String method(Some a, Other b) {
        return Stream.<Supplier<String>>of(
                // for the methods without params
                myClass::check1,
                // for the methods with params
                () -> check2(a),
                () -> check3(a, b))
        .map(Supplier::get)
        .filter(StringUtils::isNotBlank)
        .findFirst().orElse("");
}

Upvotes: 1

Views: 237

Answers (2)

WJS
WJS

Reputation: 40047

You said you thought of stream filter first but a stream is not really required in this case.

  • creating a list of suppliers is the clearly the right approach
  • but you need to reverse the test condition and return if true (i.e. the message is not empty).
  • just continue to call the methods until the test succeeds and returns the message.
List<Supplier<String>> checkList = List.of(this::check1,
        this::check2, this::check3, this::check4, this::check5);

    
private String validate() {
    String message;
    for (Supplier<String> nextCheck : checkList) {
        if (!(message = nextCheck.get()).isEmpty()) {
            return message;
        }
    }
    return "";
}

Note that you could have achieved the same optimization using your initial approach but changing the condition.

public String validiate() {
    String message;
    if (!(message = check1()).isEmpty()) {
        return message;
    }
    if (!(message = check2()).isEmpty()) {
        return message;
    }
    if (!(message = check3()).isEmpty()) {
        return message;
    }
    
    // other message checks
    
    return "";
}

Upvotes: 1

M A
M A

Reputation: 72884

Instead of constructing a Stream of Strings, you can do a Stream of Supplier<String>, which would defer the invocation of the method until the filter needs to be checked in the stream pipeline:

return Stream.<Supplier<String>>of(this::check1, this::check2, this::check3, ...)
            .filter(check -> !check.get().isEmpty())
            .findFirst()
            .map(Supplier::get)
            .orElse("");

A better variant is to do the mapping from Supplier to the result String before the filter (the advantage is that this won't call the method with the non-empty result twice):

return Stream.<Supplier<String>>of(this::check1, this::check2, this::check3, ...)
            .map(Supplier::get)
            .filter(message -> !message.isEmpty())
            .findFirst()
            .orElse("");

Upvotes: 3

Related Questions