Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Benefits of @FunctionalInterface in staged builders

Introduction

While searching the web I stumbled upon a blog post by Benoit Tellier, Next level Java 8 staged builders, where he shares his variant of the staged builder pattern for certain use cases.

I noticed that stages are annotated with @FunctionalInterface. Here's an example from his post (without the technique):

public static class MailboxCreatedBuilder {
    @FunctionalInterface
    public interface RequireUser {
        RequireSessionId user(User user);
    }

    @FunctionalInterface
    public interface RequireSessionId {
        RequireMailboxId sessionId(MailboxSession.SessionId sessionId);
    }

    @FunctionalInterface
    public interface RequireMailboxId {
        FinalStage mailboxId(MailboxId mailboxId);
    }

    public static class FinalStage {
        ...
        ...
    }

    public static RequireUser builder() {
        return user -> sessionId -> mailboxId -> new FinalStage(user, sessionId, mailboxId);
    }
}

This annotation limits the number of methods a stage can have to one, plus overloaded methods with a default implementation. It's probably a good idea that each stage deals with one property anyway, but for my current needs I'd like to have multiple methods with implementations in a separate class.

Question

It got me wondering though: should my stages have @FunctionalInterface too? What are the benefits/how would such a builder be used in functional style programming?

Edit

Based on the comments below this question, it turns out what I really wanted to know is what the needs/benefits are of making stages adhere to the functional interface contract (regardless of the optional annotation).

like image 212
Rolf W. Avatar asked Sep 16 '25 09:09

Rolf W.


2 Answers

The single-method required-stage types are for forcing the caller to supply required values, enforced by the compiler (which enforces a particular order too). The FinalStage class then has all the optional methods.

In this case, after calling builder(), you must call user(...), then sessionId(...), then mailboxId(...), and finally any optional methods defined in FinalStage:

MailboxCreatedBuilder.builder()
        .user(...)
        .sessionId(...)
        .mailboxId(...)
        ...
        .build();
like image 82
Andreas Avatar answered Sep 17 '25 22:09

Andreas


In this pattern, your RequireUser, RequireSessionId and RequireMailboxId need to be functional interfaces in order for builder() method of the MailboxCreatedBuilder type to look like

public static RequireUser build() {
    return user -> sessionId -> mailboxId -> new FinalStage(user, sessionId, mailboxId);
}

If any of the stages doesn't follow functional interface contract (i.e. it's not a single-abstract-method interface), then this chain breaks, and you'll need a concrete non-abstract class for that stage, and probably all stages that follow it.

The @FunctionalInterface annotation here is optional in this pattern (since you can make lambdas out of any interface with a single abstract method), and it's intended to notify both your colleagues and compiler that these classes are indeed required to be functional interfaces for the whole setup to work. Having the annotation benefits you by the fact that all Java compilers are required by JLS to verify the functional interface contract on types that have that annotation, so you'll get nice compiler errors when you accidentally break the contract.

like image 23
M. Prokhorov Avatar answered Sep 17 '25 22:09

M. Prokhorov