4

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

Rolf W.
  • 1,379
  • 1
  • 15
  • 25
  • Specifically, that annotation is only there to make sure each stage has a single abstract method on it, so that each stage can be implemented with a lambda. It is a part of contract that saves you lines in implementing individual classes, and tells other people that you really intend it to work that way. – M. Prokhorov Jul 30 '19 at 13:06
  • Reading the article itself, I'd say it outlines the benefits and drawbacks pretty well just by itself. You don't *need* any of it by, conceptually, but if you want staged builders like that, then annotating would work well in what I described above. – M. Prokhorov Jul 30 '19 at 13:14
  • Ah, of course... So this wouldn't be possible otherwise: `return user -> sessionId -> mailboxId -> new FinalStage(user, sessionId, mailboxId);`, right? – Rolf W. Jul 30 '19 at 13:23
  • No, you can still make lambdas from the interface that has single abstract method and no `@FunctionalInterface` annotation on it. What that annotation does is it tells you that this interface is indeed intended to only have single method, and adding or removing non-default methods will break your contract. This contract will also be verified by compilers, because JLS requires that. But that last code where `FinalStage` is created wouldn't be possible if for instance `RequireUser` didn't follow the functional interface contract and had two abstract methods. – M. Prokhorov Jul 30 '19 at 13:26
  • I mean: do the stages need to be functional interfaces (regardless of the presence of the annotation) in order for the lamba in the static `builder()` method to work? – Rolf W. Jul 30 '19 at 13:33
  • For `builder()` method implementation to look the way it does, all stages need to follow functional interface contract, so yes. Other way this might work is if you made concrete classes to implement each stage, but you will likely write more code that way (unless you work out how to reuse builder stages, which should be easier with classes rather than stages). – M. Prokhorov Jul 30 '19 at 13:37
  • That's the answer to what turns out to be my actual question (I'll make an edit); If you could write that as a full answer then I'll accept that. – Rolf W. Jul 30 '19 at 13:55

2 Answers2

2

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();
Andreas
  • 154,647
  • 11
  • 152
  • 247
  • This is a good explanation of how the staged builder pattern works and how it's used. My question was about the benefits of `@FunctionalInterface` in this pattern though. And as it turns out my question was actually about why stages need to follow the functional interface contract :) If you could edit your answer to incorporate the comment you made below my question then I'll make this the accepted answer. – Rolf W. Jul 30 '19 at 13:45
  • @RolfW. I didn't make any comments below your question. – Andreas Jul 30 '19 at 13:49
  • @RolfW. *"my question was actually about why stages need to follow the functional interface contract"* Well, they don't need that, it's just easier that way, since you don't explicitly have to carry captured values forward to the next stage. – Andreas Jul 30 '19 at 13:51
  • Oh, I see. Got things mixed up :) – Rolf W. Jul 30 '19 at 13:52
1

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.

M. Prokhorov
  • 3,894
  • 25
  • 39
  • That's the `builder()` method, not the `build()` method, and it's of the `MailboxCreatedBuilder` class, not the `FinalStage` class, as *clearly evident* from the fact that it's a non-static method that creates a `FinalStage` object. – Andreas Jul 30 '19 at 15:41
  • @Andreas, yes, I was distracted and mixed up the two classes. It's fixed now. – M. Prokhorov Jul 30 '19 at 18:20