Search code examples
javafunctional-programmingfunctional-interface

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).


Solution

  • 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.