How to create reusable optional mapping with method references?

Viewed 439

While I'm trying to use Optional features with method references, it really confused me how to optimize it with reusable code. I think I'm stuck while trying to use all those new features (for me) at the same time i decided to get rid of java-6 style, now I think i can't think simple, i feel that it gets overcomplicated. How can i create

List<BooleanExpression> expressionMapping = new ArrayList<>();

    if (request != null) { // request is input parameter, a DTO

        Optional.ofNullable(request.getPlantId())
            .map(campaign.plant.id::contains) // campaign is static created by Querydsl
            .ifPresent(expressionMapping::add);

        Optional.ofNullable(request.getTitle())
            .map(campaign.title::containsIgnoreCase)
            .ifPresent(expressionMapping::add);

        Optional.ofNullable(request.getCampaignNumber())
            .map(this::getLikeWrapped)
            .map(campaign.campaignNumber::like)
            .ifPresent(expressionMapping::add);
... 20 more Optional bunch of code like this
}

also having trouble with writing this code with Optional like previous ones:

if (request.getLockVehicle() != null) {
            if (request.getLockVehicle()) {
                expressionMapping.add(campaign.lockVehicle.isNotNull());
            } else {
                expressionMapping.add(campaign.lockVehicle.isNull());
            }
        }
2 Answers

What about use enum to declare all fields from Request and use it as common part of the code. I did not check it, this is only to show my approach:

public enum RequestField {
    PLANT_ID(Request::getPlantId, (val, campaign) -> campaign.plant.id::contains),
    TITLE(Request::getTitle, (val, campaign) -> campaign.title::containsIgnoreCase),
    CAMPAIGN_NUMBER(Request::getCampaignNumber, (val, campaign) -> campaign.campaignNumber::like),
    // ... more fields here ...
    ;

    private final Function<Request, Optional<Object>> get;
    private final BiFunction<Object, Campaign, BooleanExpression> map;

    RequestField(Function<Request, Object> get, BiFunction<Object, Campaign, BooleanExpression> map) {
        this.get = get.andThen(Optional::ofNullable);
        this.map = map;
    }

    public static List<BooleanExpression> getBooleanExpressions(Request request, Campaign campaign) {
        if (request == null)
            return Collections.emptyList();

        List<BooleanExpression> res = new LinkedList<>();

        for (RequestField field : values())
            field.get.apply(request)
                     .map(r -> field.map.apply(r, campaign))
                     .ifPresent(res::add);

        return res.isEmpty() ? Collections.emptyList() : Collections.unmodifiableList(res);
    }
}

And your client code will be looking like:

List<BooleanExpression> booleanExpressions = RequestField.getBooleanExpressions(request, campaign);

P.S. Your last code could be look like:

if (request.getLockVehicle() != null)
    expressionMapping.add(request.getLockVehicle() ? campaign.lockVehicle.isNotNull() : campaign.lockVehicle.isNull());

The aim of using Optional is informing who is calling that method / parameter that it could be null.

In the first part of your code, you are not getting any advantage from this, you are just rewriting some code wrapping it around Optional logic but, as you said, without any "reusable" purpose.

A useful way is using it as returning value of a method: for example, if you know that your title could be null, you can refactor your getter like

public Optional<String> getTitle(){
    return Optional.ofNullable(this.title); //I'm guessing the 'title' variable here
}

This will help you: every time you call getTitle() , you will know that could be null, because you are obtaining an Optional<String> instead of a String.

This will bring then you to:

request.getTitle().ifPresent(title-> title.doSomething())
// you can also add something like .orElse("anotherStringValue")

The second example could be reworked as the first one, making the return of getLockVehicle() as Optional<Boolean>, even if I suggest here setting that with a default value in your class, probably to false... Optional<Boolean> is pretty senseless imho

Hope this helps clearing your mind

Related